美文网首页iOS面试知识点iOS 面试题iOS开发攻城狮的集散地
17·iOS 面试题·描述一下触摸事件传递流程

17·iOS 面试题·描述一下触摸事件传递流程

作者: pengxuyuan | 来源:发表于2018-10-18 17:00 被阅读7次

前言

在 iOS 中,常见的事件有:触摸事件、加速计事件、远程控制事件等。在这里我们主要讨论触摸事件,对于触摸事件的传递流程,我们需要先了解响应者对象和响应者链是什么,这样子才可以更加清晰的认识事件的传递流程和响应流程,然后再利用这些知识点来解决业务需求。

响应者对象

只有响应者对象才可以接收处理事件,在 iOS 中,只有 UIResponder 及其子类称为响应者对象,平时我们的 UIApplicationUIViewControllerUIView 都是继承自 UIResponder,所以它们都是响应者对象,可以接收处理事件。对于 CALayer 不是继承自 UIResponder 的,这就是为什么 CALayer 没有响应事件的能力。

对于触摸事件,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;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

触摸事件的产生和传递

用户触摸屏幕产生事件,系统将事件交给 UIApplication 管理分发,UIApplication 将事件分发给 KeyWindow,然后再寻找出一个最合适的响应者来响应这个事件。

如何寻找出最合适的响应者,主要依靠下面两个函数:

//返回最合适的 View 来响应事件
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;  
// 判断当前的触摸点是否在 View 中
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; 

这里引用 初探 iOS 事件分发机制 解释:

Hit-Test View:当用户与触摸屏产生交互时,硬件就会探测到物理接触并且通知操作系统。操作系统就会创建相应的事件,并将其传递给当前正在运行的应用程序的事件队列。然后这个事件会被事件循环传递给优先响应对象,既 Hit-Test View

Hit-Testing:Hit-Test View 就是事件被触发时和用户交互的对象,寻找 Hit-Test View 的过程就叫做 Hit-Testing

现在我们知道事件的传递是靠上面两个方法来寻找最合适的响应者,找到响应者后会调用响应者的 touch 函数进行事件处理,大概流程是:

产生触摸事件 -> UIApplication 事件队列 -> [UIWindow hitTest:withEvent:] -> 返回更合适的view -> [子控件 hitTest:withEvent:] -> 返回最合适的view -> [Application sendEvent] -> 调用最合适 view 的 touch 函数处理事件

响应者链及事件响应流程

页面的控件具有层级关系,响应者也会有层级关系,由响应者组成层级关系称为响应者链。UIResponder 中有个 nextResponder 属性返回下一个响应者对象。当一个响应者接收到事件但是不能处理时候,会交给下一个响应者去处理,最终要是谁都处理不了该事件,则会抛弃这个事件。

对于响应者链,可以参考下图:

事件传递和事件响应区别

事件传递是从父控件到子控件传递,从上到下;事件响应是顺着响应者链向上传递(从子控件到父控件),从下到上。

实战-子视图和父视图同时处理事件

子视图重写 touch 函数来处理事件,然后再调用 super touch 将事件传递给父视图:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);
    //子视图处理该事件
    
    //调用 super 让父视图也处理该事件
    [super touchesBegan:touches withEvent:event];
}

实战-扩大一个视图的点击范围

可以通过 pointInside 函数,将该视图周围的触摸事件也当成自己的事件处理:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect relativeFrame = self.bounds;
    UIEdgeInsets hitTestEdgeInsets = UIEdgeInsetsMake(-15, -15, -15, -15);
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

实战-深层级 View 通讯

假设控制器上面添加 AView,AView 添加了 BView,BView 又添加了 CView 等,在 CView 产生了一个事件需要让控制器来处理,这个时候如果用 Block、Delegate、Notification 都会比较麻烦,这个时候可以通过响应者链,将消息传递上去。

  1. 首先我们为 UIResponder 写个分类方法,类似 Router 方法
  2. 只需要在 CView 中调用该方法,让控制器去监听该方法就 OK 了

具体代码实现:

//UIResponder 分类实现
/**
 发送一个路由器消息, 对eventName感兴趣的 UIResponsder 可以对消息进行处理
 
 @param eventName 发生的事件名称
 @param userInfo 传递消息时, 携带的数据, 数据传递过程中, 会有新的数据添加
 */
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSObject *)userInfo {
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}

//CView 调用
[self routerEventWithName:@"CViewEvent" userInfo:nil];

//控制器监听
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSObject *)userInfo {
    NSLog(@"%s eventName:%@",__func__,eventName);
}

实战-HitTest 大概实现

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha <= 0.01 || self.userInteractionEnabled == NO || self.hidden) {
        return nil;
    }
    
    BOOL inside = [self pointInside:point withEvent:event];
    if (inside) {
        NSArray *subViews = self.subviews;
        // 对子视图从上向下找
        for (NSInteger i = subViews.count - 1; i >= 0; i--) {
            UIView *subView = subViews[i];
            CGPoint insidePoint = [self convertPoint:point toView:subView];
            UIView *hitView = [subView hitTest:insidePoint withEvent:event];
            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
    return nil;
}

总结

这篇我们主要了解了响应者对象是什么,事件的传递流程以及事件响应流程。了解了这些知识后,还是对我们平时开发有所帮助的。

对于更加详细的介绍,可以看看后面的博客链接。

参考文献

史上最详细的iOS之事件的传递和响应机制-原理篇

Hit-Testing in iOS

深入浅出iOS事件机制

iOS事件处理,看我就够了~

初探 iOS 事件分发机制

相关文章

  • 17·iOS 面试题·描述一下触摸事件传递流程

    前言 在 iOS 中,常见的事件有:触摸事件、加速计事件、远程控制事件等。在这里我们主要讨论触摸事件,对于触摸事件...

  • iOS 触摸事件与响应理解

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

  • 事件层级原理 响应链

    iOS事件 运动事件,远程控制事件、触摸事件 触摸事件 事件传递的顺序 当点击红色的时候 打印为 事件传递的方法 ...

  • iOS 响应链

    iOS开发 - 事件传递响应链iOS 响应者链,事件的传递事件传递之响应链Cocoa Touch事件处理流程--响...

  • Android基础(28)事件分发

    1)请描述一下View事件传递分发机制2)Touch事件传递流程3)事件分发中的onTouch 和onTouchE...

  • iOS开发之触摸事件

    本文介绍了iOS中使用频率较高的触摸事件,并阐述了事件产生和传递的过程,以及响应者链的事件传递过程 触摸事件 简介...

  • iOS事件,原来如此

    精简地说:iOS事件分为传递和响应两个部分。 事件传递(建立传递链): iOS系统检测到手指触摸(Touch)操作...

  • iOS触摸事件传递

    UIResponder、UIGestureRecognizer、UIControl UIResponder UIV...

  • 2019 iOS面试题大全---全方面剖析面试(上)

    2018 iOS面试题---UI相关:事件传递,图像显示,性能优化,离屏渲染 2018 iOS面试题---Obje...

  • 2019 iOS面试题大全

    1、2018 iOS面试题---UI相关:事件传递,图像显示,性能优化,离屏渲染 2、2018 iOS面试题---...

网友评论

    本文标题:17·iOS 面试题·描述一下触摸事件传递流程

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