OC面试相关

iOS触摸事件响应原理

2020-12-31  本文已影响0人  NingSpeals

系统响应阶段

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

APP响应触摸事件

触摸 事件 响应者

触摸

触摸对象即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,到底应该谁去响应呢?寻找最佳响应者就是找出这个优先级最高的响应对象。

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

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

寻找最佳响应者的原理

hitTest:withEvent:

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

开始时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这个方法中完成的。该方法默认的实现是将该方法沿着响应链往下传递
响应者对于接受到的事件有三种操作:

响应链中的事件传递规则

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

UIControl

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

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

猜你喜欢

热点阅读