美文网首页OC面试相关
iOS触摸事件响应原理

iOS触摸事件响应原理

作者: NingSpeals | 来源:发表于2020-12-31 16:27 被阅读0次

系统响应阶段

  • 1.手指触摸屏幕,屏幕感受到触摸后,将事件交给IOKit来处理。
  • 2.iOKit将触摸事件封装成iOHIDEvent对象,并通过mach port传递给SpringBoard进程。

mach port是进程端口,各进程间通过它来通信。Springboard是一个系统进程,可以理解为桌面系统,可以统一管理和分发系统收到的触摸事件。

  • 3.Springboard由于接收到触摸事件,因此触发了系统进程的主线程的runloopsource回掉。发生触摸事件的时候,你有可能正在桌面上翻页,也有可能正在头条上看新闻,如果是前者,则触发Springboard主线程的runloopsource0回调,将桌面系统交给系统进程去消耗。而如果是后者,则将触摸事件通过IPC传递给前台app进程,后面的事便是APP内部对于触摸事件的响应了。

APP响应触摸事件

  • 1.APP进程的mach port接收来自Springboard的触摸事件,主线程runloop被唤醒,触发source1回调。

  • 2.source1回调又触发了一个source0回调,将接收到的IOHIDEvent对象封装成UIEvent对象,此时APP将正式开始对于触摸的响应。

  • 3.source0回调将触摸事件添加到UIApplication的事件队列,当触摸事件出队后UIApplication为触摸事件寻找最佳响应者。

  • 4.寻找到最佳响应着之后,接下来的事情便是事件在响应链中传递和响应。

触摸 事件 响应者

触摸

触摸对象即UITouch对象。一个手指触摸屏幕,就会生成一个UITouch对象,如果多个手指同时触摸,就会生成多个UITouch对象。多个手指先后触摸,如果系统判断多个手指触摸的是同一个地方,那么不会生成多个UITouch对象,而是更新这个UITouch对象,改变其tap count。如果对歌手指触摸的不是同一个地方,那么就会产生对个UITouch对象。

触摸事件

触摸事件即UIEventUIEvent即对UITouch的一次封装。由于一次触摸事件并不止有一个触摸对象,可能是多指同时触摸。触摸对象集合可以通过allUITouchs属性来获取。

响应者

响应者即UIResponserUIViewUIViewControllerUIApplicationUIAppdelegate等实例都是UIResponser,响应者响应触摸事件是由下面的方法来实现的:

//手指触碰屏幕,触摸开始
- (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;

寻找最佳响应者

APP通过mach port得到这个触摸事件时,APP中有那么多UIView或者UIViewController,到底应该谁去响应呢?寻找最佳响应者就是找出这个优先级最高的响应对象。

  • 寻找最佳响应着的具体流程如下:
  • 1.UIApplication首先将事件传递给窗口对象(UIWindow),如果有多个UIWindow对象,则先选择最后加上的UIWindow对象。
  • 2.若UIWindow对象能响应这个触摸事件,则继续向其子视图传递,向子视图传递时也是先传递给最后加上的子视图。
    1. 若子视图无法响应该事件,则返回父视图,再传递给倒数第二个加入该父视图的子视图。

视图如何判断自己能否响应触摸事件?

以下情况不能响应触摸事件:

  • 1.触摸点不在视图范围内。
  • 2.不允许交互: 视图的userInteractionEnabled = NO
  • 3.隐藏:hidden = YES,如果视图隐藏了,则不能响应事件。
  • 4.透明度:当视图的透明度小于等于0.01时,不能响应事件。

寻找最佳响应者的原理

hitTest:withEvent:

每个UIView都有一个hitTest:witnEvent:方法。这个方法是寻找最佳响应者的核心方法,同时又是传递事件的桥梁。它的作用是询问事件在当前视图中的响应者。hitTest:withEvent:返回一个UIView对象,作为当前视图层次中的响应者。其默认实现是:

  • 若当前视图无法响应事件,则返回nil。
  • 若当前视图能响应事件,但无子视图可响应事件,则返回当前视图。
  • 若当前视图能响应事件,同时有子视图能响应,则返回子视图层次中的事件响应者。

开始时UIApplication调用UIWindowhitTest:wuithEvent:方法将触摸事件传递给UIWindow,如果UIWindow能够响应触摸事件,则调用hitTest:withEvent:将事件传递给其子是视图并询问子视图上的最佳响应者,这样一级一级传递下去,获取最终的最佳响应者。
hitTest:withEvent:的代码实现大致如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //3种状态无法响应事件
     if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil; 
    //触摸点若不在当前视图上则无法响应事件
    if ([self pointInside:point withEvent:event] == NO) return nil; 
    //从后往前遍历子视图数组 
    int count = (int)self.subviews.count; 
    for (int 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) 
        {
            //如果子视图中有更合适的就返回
            return fitView; 
        }
    } 
    //没有在子视图中找到更合适的响应视图,那么自身就是最合适的
    return self;
}

注意这里的方法pointInside:withEvent:,这个方法是判断触摸点是否在视图范围内。默认的实现是如果触摸点在视图范围内则返回YES,否则返回NO
下面我们在上图中的每个视图层次中添加三个方法来验证之前的分析:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
    return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
    return [super pointInside:point withEvent:event];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
}

点击视图,打印出来的结果是:

-[AView hitTest:withEvent:]
-[AView pointInside:withEvent:]
-[CView hitTest:withEvent:]
-[CView pointInside:withEvent:]
-[EView hitTest:withEvent:]
-[EView pointInside:withEvent:]
-[EView touchesBegan:withEvent:]

这和我们的分析是一致的。

触摸事件的响应

通过hitTest:withEvent:已经找到了最佳响应者,现在要做的事情是让这个最佳响应者触摸事件。这个最佳响应者对于触摸事件拥有决定权,它可以决定是自己响应这个事件,也可以自己响应之后还把它传递给其他响应者,这个响应者构成的就是响应链。
响应者对于事件的响应和传递都是在touchesBegan:withEvent这个方法中完成的。该方法默认的实现是将该方法沿着响应链往下传递
响应者对于接受到的事件有三种操作:

  • 1.默认的操作。不拦截,事件会沿着默认的响应链自动往下传递。
  • 2.拦截,不在往下分发事件,重写touchesBegan:withEvent:方法,不调用父类的touchesBegan:withEvent:方法。
  • 3.不拦截,继续往下分发事件,重新touchesBegan:withEvent方法,并调用父类touchesBegan:withEvent:方法。
    我们一般在编写代码时,如果某个视图响应事件,会在该视图类中重写touchesBegan:withEvent:方法,但是并不会调用父类的touchesBegan:withEvent:方法,这样我们就把这个事件拦截下来,不在沿着响应链往下传递。那么我们为什么想要沿着响应链传递事件就要写父类的touchesBegan:withEvent:方法呢?因为父类的touchesBegan:withEvent:方法默认是向下传递的。重写touchesBegan:withEvent:并调用父类的方法就是既对触摸事件实现了响应,又将事件沿着响应链传递了。

响应链中的事件传递规则

每一个响应者对象都又一个nextResponder方法,用来获取响应链中当前响应者对象的下一个响应者。硬刺,如果事件的最佳响应者确定了,那么整个响应链也就确定了。
对于响应者对象,默认的nextResponse对象如下:

  • UIView
    若视图是UIViewController的view,则其nextResponder是UIViewController,若其只是单独的视图,则其nextResponser是其父视图。
  • UIViewController
    若该视图是window的根视图,则其nextResponser是UIViewController,若其是由其他视图控制器present的,则其nextResponder是presenting view controller。
  • UIWindow
    nextResponder为UIApplication对象。
    如果最佳响应者对象是UITextField,则响应链为:UITextField->UIView->UIView->UIViewController->UIWindow->UIApplication->UIApplicationDelegate.在父类的touchesBegan:withEvent:方法中,可能调用了[self.nextResponder touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event]这样来将事件沿着响应链传递。

UIControl

UIControl是系统提供的能够以target-action模式处理触摸事件的控件,iOS中UIButtonUISegmentedControlUISwitch等控件都是UIControl的子类。当UIControl跟踪到触摸事件时,会向其上添加的target发送事件以执行actionUIConotrol是UIView的子类,因此本身也具备UIResponder应有的身份。

UIControl会阻止父视图上的手势识别器的行为,也就是UIControl的执行优先级比父视图上面的UIGestureRecognizer要高,但是比UIControl自身的UIGestureRecognizer优先级要低。

相关文章

  • ios 事件传递和响应

    史上最详细的iOS之事件的传递和响应机制-原理篇iOS触摸事件传递响应之被忽视的手势识别器工作原理手势事件中can...

  • 初识iOS事情处理机制

    参考:史上最详细的iOS之事件的传递和响应机制-原理篇iOS触摸事件全家桶史上最详细的iOS之事件的传递和响应机制...

  • iOS触摸事件响应原理

    系统响应阶段 1.手指触摸屏幕,屏幕感受到触摸后,将事件交给IOKit来处理。 2.iOKit将触摸事件封装成iO...

  • iOS 触摸事件与响应理解

    参考文章: iOS触摸事件的流动 iOS触摸事件的传递与响应 UIViewController UIAppli...

  • 二、事件传递链和响应者链

    iOS触摸事件详解iOS开发-事件传递响应链 响应者链 UIResponser包括了各种Touch message...

  • UIView响应链及事件穿透

    参考:iOS触摸事件全家桶史上最详细的iOS之事件的传递和响应机制-原理篇 1、同一层级,让上面的一层不响应,让被...

  • iOS 触摸事件的处理层次及原理

    iOS 事件包括:运动事件、远程控制事件、触摸事件。 其中触摸事件的响应流程是:当手指触摸屏幕时,会产生一个事件,...

  • iOS事件的响应者链

    iOS 事件响应者链 1 iOS中的事件 触摸事件 加速计事件 远程控制事件 在iOS中不是任何对象都能处理事件,...

  • iOS事件处理

    iOS中常用的事件 触摸事件 加速计事件 远程控制事件 什么是响应者对象 继承了UIResponds的对象为响应者...

  • 事件处理

    事件处理 ios中的事件触摸事件(捏合,点击)加速计事件远程控制事件(耳机线控调整音量大小) 触摸事件 响应者对象...

网友评论

    本文标题:iOS触摸事件响应原理

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