美文网首页
iOS 事件传递与响应者链

iOS 事件传递与响应者链

作者: 一片姜汁 | 来源:发表于2018-05-16 11:36 被阅读26次

    一.基本概念

    • UIEvent

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

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

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

    • UIResponder

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

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

    二.事件的产生和传递

    • 产生和传递的过程

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

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

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

    • hitTest:withEvent的内部逻辑

    流程图.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

    • 实例

    示例.png

    1.视图层级


    视图层级.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,就会沿着响应链进行传递,以对事件做出响应。响应链的传递顺序是【子到父】

    响应链示例.png
    响应顺序:如果触摸发生在UITextField上面->UIView->UIView->UIViewController->UIWindow->UIApplication->UIApplicationDelegate
    事件的响应顺序为:子到父
    • 响应事件的方法

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

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//手指按下
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//手指移动
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//手指抬起
    - (void)touchesCancelled:(NSSet<UITouch *> *)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。
    • 示例
      示例.png
      在图中的每一个View的touchsBegan:withEvent中实现:
    - (void)touchesBegan:(NSSet<UITouch *> *)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 事件传递与响应者链

          本文链接:https://www.haomeiwen.com/subject/ofxzrftx.html