iOS 事件传递与响应者链

一.基本概念

  • UIEvent

一个UIEvent对象描述一次用户交互行为。例如:用户点击手机屏幕、摇晃手机以后,系统都会收到UIEvent事件。本文仅介绍Touch事件。

  **事件类型(UIEventType)**:
    UIEventTypeTouches触摸事件
    UIEventTypeMotion加速计、传感器...
    UIEventTypeRemoteControl耳机线控...
    UIEventTypePresses遥控器或者其他设备(例如游戏控制器)...
  • UITouch

当用户用手指点击手机屏幕时,会产生UITouch对象。UITouch对象包括了触摸的时间、位置、阶段、所处的视图、窗口等信息。

  • UIResponder

可以响应和处理事件的对象。UIResponder之所以能处理事件,是因为其包含以下四个方法【此处的事件仅指触摸事件】:

- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event;//手指按下
- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event;//手指移动
- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event;//手指抬起
- (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event;//触摸事件被取消【手机来电话了系统会自动调用这个方法取消触摸事件,或者其他原因】

二.事件的产生和传递

  • 产生和传递的过程

1.用户触摸屏幕,会产生一个触摸事件,该触摸事件会被加入到UIApplication管理的事件队列。【队列的特点是先入先出,先产生的事件系统会优先处理】
2.UIApplication取出最前面的事件进行分发【分发的顺序:父->子】,目的是为了寻找最适合处理此事件的对象。

  • 如何寻找最适合处理此事件的对象

视图收到触摸事件以后,系统会调用hitTest:withEvent判断视图是不是最适合处理此触摸事件的对象。

  • hitTest:withEvent的内部逻辑

iOS 事件传递与响应者链_第1张图片
流程图.jpg

代码实现:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2. 判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    // 3.从后往前遍历自己的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        // 把当前控件上的坐标系转换成子控件上的坐标系
        CGPoint childP = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) { // 寻找到最合适的view
            return fitView;
        }
    }
    // 循环结束,表示没有比自己更合适的view
    return self;
}

UIView不同接收触摸事件的三种情况
a. userInteractionEnabled = NO
b.hidden = YES
c.alpha <= 0.01

  • 实例

iOS 事件传递与响应者链_第2张图片
示例.png

1.视图层级


iOS 事件传递与响应者链_第3张图片
视图层级.png

2.在每一个View和自定义的窗口中实现hitTest:withEvent方法,控制台输出如下:

CustomWindow-hitTest:withEvent://窗口
EventPropagattedView-hitTest:withEvent://ViewController_View
YView-hitTest:withEvent:
GView-hitTest:withEvent:
RView-hitTest:withEvent:
BView-hitTest:withEvent:

从而得知事件传递顺序如下:

UIWindow->ViewController_View->YView->GView->RView->BView

三.事件响应

  • 响应链的响应顺序

响应者链:继承自UIResponder的对象组合以来的链条。
经过事件传递以后,系统找到了最适合处理此事件的View,就会沿着响应链进行传递,以对事件做出响应。响应链的传递顺序是【子到父】

iOS 事件传递与响应者链_第4张图片
响应链示例.png

响应顺序:如果触摸发生在UITextField上面->UIView->UIView->UIViewController->UIWindow->UIApplication->UIApplicationDelegate
事件的响应顺序为:子到父

  • 响应事件的方法

UIResponder通过四个方法对事件进行响应:

- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event;//手指按下
- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event;//手指移动
- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event;//手指抬起
- (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event;//触摸事件被取消【手机来电话了系统会自动调用这个方法取消触摸事件,或者其他原因】

系统默认在这四个方法里面是不对事件进行处理的,只是默认实现事件的响应传递。若要实现自定义的响应操作,则需要重写这四个方法。每一个响应者对象(UIResponder对象)都有一个 nextResponder 方法,用于获取响应链中当前对象的下一个响应者。因此,一旦事件的最佳响应者确定了,这个事件所处的响应链就确定了。nextResponder如下:

  • UIView
    若视图是控制器的根视图,则其nextResponder为控制器对象;否则,其nextResponder为父视图。
  • UIViewController
    若控制器的视图是window的根视图,则其nextResponder为窗口对象;若控制器是从别的控制器present出来的,则其nextResponder为presenting view controller。
  • UIWindow
    nextResponder为UIApplication对象。
  • UIApplication
    若当前应用的app delegate是一个UIResponder对象,且不是UIView、UIViewController或app本身,则UIApplication的nextResponder为app delegate。
  • 示例
    iOS 事件传递与响应者链_第5张图片
    示例.png

    在图中的每一个View的touchsBegan:withEvent中实现:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@-%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
    [super touchesBegan:touches withEvent:event];
}

点击BlueView控制台输出如下:

BView-touchesBegan:withEvent:
RView-touchesBegan:withEvent:
YView-touchesBegan:withEvent:
EventPropagattedView-touchesBegan:withEvent:
EventPropagattedViewController-touchesBegan:withEvent:
CustomWindow-touchesBegan:withEvent:

四.总结

  • 事件的传递顺序

从父到子,如果父不能接收触摸事件,那么子也不能接收触摸事件。

  • 事件的响应顺序

从子到父

你可能感兴趣的:(iOS 事件传递与响应者链)