iOS面试知识点iOS 面试题iOS开发攻城狮的集散地

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

2018-10-18  本文已影响7人  pengxuyuan

前言

在 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 事件分发机制

上一篇 下一篇

猜你喜欢

热点阅读