事件传递机制

木木木大约 7 分钟iOSUI交互

事件传递机制

iOS中用户的操作会被转换为UI事件,并由系统传递给应用程序。这个过程涉及到多个对象的交互,其中包括UIApplication、UIWindow、UIView和UIResponder。

事件的传递过程

当用户在屏幕上进行操作时,如点击按钮或滑动屏幕,这些操作将被转换为UI事件。这些事件将从UIApplication对象开始传递,经过UIWindow和UIView,最终到达事件的目标响应对象。

事件传递机制是一个基于响应链的过程。当用户执行某个操作时(例如点击屏幕),系统会将该操作转换为一个或多个 UI 事件,并将其传递给应用程序。

iOS 中的 UI 事件传递机制是基于响应链实现的,事件会从顶层容器 UIWindow 开始,逐级传递到下一级容器或视图中,直到找到能够响应该事件的视图或容器为止。在这个过程中,每个视图都可以判断是否能够响应该事件,并处理该事件。对于多手指触摸事件,iOS 会将这些事件封装成一个 UIEvent 对象,并传递给多个视图进行处理。

这些事件的传递过程包括以下步骤:

UIApplication 对象接收到事件

UIApplication 对象是所有 iOS 应用程序的入口点,它负责处理与应用程序相关的事件。当一个事件发生时,首先会被 UIApplication 对象接收到。

UIWindow 接收事件

UIWindow 是 iOS 中的顶层容器,所有的视图都必须添加到 UIWindow 上。在应用程序中,UIApplication 会将事件传递给当前的 UIWindow 实例,即应用程序窗口。UIWindow 对象是一个 UIResponder 对象,因此它能够响应接收到的事件。

事件传递给视图

在接收到事件之后,UIWindow 会将事件传递给它的第一个子视图。如果该视图能够响应该事件,则事件传递停止,该视图会处理该事件。如果该视图无法响应该事件,则事件会继续传递给该视图的下一个子视图。

视图判断是否能够响应事件

当事件被传递给一个视图时,该视图会判断是否能够响应该事件。如果该视图能够响应该事件,则该事件将被处理,事件传递停止。否则,该视图会将该事件传递给它的子视图。

事件递归传递至下一级视图

如果该视图无法响应该事件,则该事件会递归地传递给它的子视图,直到找到能够响应该事件的视图为止。这个过程就是响应链的传递过程。

事件传递顺序

在响应链中,事件传递顺序是从顶层容器开始,逐级向下传递。因此,如果一个子视图覆盖了其父视图的一部分区域,则当该区域接收到事件时,事件将传递给该子视图,而不是其父视图。

响应链

iOS中,UIView和UIViewController都是继承自UIResponder类,因此都可以处理UI事件。
UIView和UIViewController对象可以重写以下方法来响应事件:

  • touchesBegan:withEvent: 当用户触摸屏幕时调用
  • touchesMoved:withEvent: 当用户在屏幕上移动手指时调用
  • touchesEnded:withEvent: 当用户停止触摸屏幕时调用
  • touchesCancelled:withEvent: 当触摸被取消时调用,如应用程序收到来电或者用户按下Home按钮

除了UIResponder对象定义的方法之外,还可以通过手势识别器来处理用户的手势操作。
手势识别器是UIGestureRecognizer类的子类,可以用于识别用户的手势操作,如单击、双击、长按、滑动等。可以通过添加手势识别器来监听这些手势操作,并在相应的方法中处理这些操作。

Api

UIApplication

UIApplication 是 iOS 应用程序的代表,负责管理应用程序的生命周期、处理事件和通知等。在 UIApplication 的主事件循环中,会不断从系统事件队列中获取事件并进行处理。

UIApplication 继承自 UIResponder,这意味着 UIApplication 可以处理事件并将其传递给下一个响应者。

UIEvent

UIEvent 表示一个用户事件,比如点击、滑动、长按等。UIEvent 包含了一系列 UITouch 对象,每个 UITouch 对象代表了一个触摸点的状态和位置。

在处理用户事件时,UIApplication 会创建一个新的 UIEvent 对象,并将其放入事件队列中。之后,事件会被传递给应用程序中的各个 UIView 或者 UIViewController 对象。

UIView

UIView 是 iOS 中用于显示内容的基本组件,同时也是一个 UIResponder 对象。UIView 中的事件处理方法与 UIResponder 的事件处理方法是相同的。

UIView 中定义了一系列触摸事件处理方法,包括 touchesBegan:withEvent:、touchesMoved:withEvent:、touchesEnded:withEvent:、touchesCancelled:withEvent: 等等。这些方法会在接收到相应事件时被调用。

除了响应事件的方法外,还定义了 hitTest:withEvent: 和 pointInside:withEvent: 两个方法。

hitTest:withEvent:

hitTest:withEvent: 方法用于判断一个点是否在该视图及其子视图的范围内,并返回最顶层的可接收事件的视图。可以通过以下伪代码来简单描述其实现过程:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 判断是否可以接收事件
    if (!self.userInteractionEnabled || self.alpha <= 0.01 || self.hidden) {
        return nil;
    }
    // 判断是否在该视图范围内
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
    // 递归查找最顶层可接收事件的子视图
    for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
        CGPoint subPoint = [subview convertPoint:point fromView:self];
        UIView *subHitView = [subview hitTest:subPoint withEvent:event];
        if (subHitView) {
            return subHitView;
        }
    }
    // 如果没有子视图可接收事件,则返回自己
    return self;
}

hitTest:withEvent: 方法中首先判断该视图是否可以接收事件,如果不行则返回 nil。接着判断该点是否在该视图的范围内,如果不是则返回 nil。然后递归查找该视图的所有子视图,找到最顶层可接收事件的子视图并返回。

需要注意的是,UIView 的子视图是以层次结构的形式组织起来的,因此在查找最顶层可接收事件的子视图时,需要从最上层的子视图开始逐层往下查找。为了确保子视图的遍历顺序正确,上述代码中使用了 reverseObjectEnumerator 方法,将子视图的遍历顺序倒序遍历。

pointInside:withEvent:

pointInside:withEvent: 方法用于判断一个点是否在该视图的范围内。可以通过以下伪代码来描述:

Copy code
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect bounds = self.bounds;
    // 判断是否在该视图范围内
    return CGRectContainsPoint(bounds, point);
}

pointInside:withEvent: 方法中首先获取该视图的范围,然后判断该点是否在该视图的范围内。如果在范围内,则返回 YES,否则返回 NO。

需要注意的是,bounds 属性代表了该视图相对于自身坐标系的矩形区域,因此可以通过 CGRectContainsPoint 函数来判断一个点是否在该视图的范围内。

总的来说,hitTest:withEvent: 和 pointInside:withEvent: 这两个方法的源码实现是 UIView 事件传递机制的核心所在。通过分析它们的实现逻辑,我们可以更深入地了解 iOS 中的事件传递机制是如何工作的。

总结

通过深入学习 iOS 中的事件传递机制,可以更好地理解视图响应事件的过程,从而更好地设计和开发 iOS 应用程序。

上次编辑于:
贡献者: perhapsdone