事件传递和响应机制

苹果文档
https://developer.apple.com/documentation/uikit?language=objc

(一)事件传递过程
苹果注册了一个source1(基于mach port)用来接收系统事件,其回调函数为_IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸、摇晃锁屏等)发生后,首先由IOKit.framework生成一个IOHIDEvent事件,由SpringBoard接收;这个SpringBoard是一个标准的应用程序,用来管理iOS的主屏幕,除此之外像windowSever(窗口服务器)、bootstrapping(引导应用程序)以及在启动时一些初始化设置都是由这个应用程序负责的。它是iOS程序中事件的第一个接受者。只能接受少数的事件比如:按键(锁屏/静音)、加速、触摸、加速、接近传感器等几种Event,随后用mach_port转发给需要的App的进程。随后苹果注册的source1会触发回调,并调用_UIApplicationHandleEventQueue()进行应用的内部分发。
_UIApplicationHandleEventQueue()会把IOHIDEvent处理包装成UIEvent对象进行处理和分发,其中包括识别UIGesture/处理屏幕旋转/发送给UIWindow等。通常事件比如UIButton点击,touchesBegin/Move/End/Cancle事件都在这个回调中完成。
分发事件首先是寻找点击位置最合适的view:
1)首先判断keyWindow是否能接受触摸事件
2)如果能,触摸点是否在自己的有效区域内
3)从后往前遍历子控件(从数组倒序遍历),若是有符合条件的,就会重复前两个步骤与

UIView不能接受触摸的情况:
1.不接受交互userInteractionEnabled = NO;
2.隐藏hidden = YES;
3.透明度小于等于0.01 alpha = 0~0.01

寻找事件点击的最合适View过程中,事件在不断的往上层视图传递,通过:

  • (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
  • (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

视图接收事件后,会调用hitTest: withEvent:方法寻找更合适的View,先通过此方法把事件传递给子View,子控件再调用这个方法寻找,若子View没有出现合适的View,那么父View则是最合适的View
在调用hitTest: withEvent:方法后,会在内部调用pointInside: withEvent:判断是否在有效范围内,若返回YES则会继续倒序遍历子View,若没有则返回自己,若为NO,则说明不是有效区域,hitTest: withEvent:返回nil
在这里插入图片描述Hit-Test和Hit-Testing
假设用户接触了上图的View E区域,那么 iOS将会按下面的顺序反复检测 subview 来寻找 Hit-Test View
1 触摸区域视图A内,所以检测视图A的subview B和C;
2 触摸区域不在视图B内,但是在C内,所以检查视图C的subview D和E;
3 触摸区域不在D内,在E内,视图E在整个视图体系中是lowest view ,所以视图E就是Hit-Test View 。
hitTest:withEvent实现原理如下 :

- (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;
}

(二)事件响应过程过程

在找到合适的View后就会调用该View的touches方法进行响应处理具体的事件,找不到View就不会调用touches方法。
响应者链条是在事件传递过程中(UIResponder)组合形成的一个响应者链条。
响应事件查找时是和传递过程相反,若当前View为控制器View,则上一个responder为Controller,若不是控制器的View,上个响应者为parentView,一直追溯到控制器,若控制器也没有响应,则会到window,若依然没有响应则会交给UIApplication,如果都不响应事件就作废了。
在这里插入图片描述
如上图,响应者链有以下特点:
响应者链通常由initial view 开始
UIView的 nextResponder是它的superview;如果 UIView已经是其所在 UIViewController 的top view,那么 UIView 的nextResponder就是 UIViewController
UIViewController 如果有 Super ViewController ,那么它的nextResponder 为其Super ViewController 最表层的View;如果没有它的 nextResponder 就是 UIWindow
UIWindow 的contentView 指向 UIApplication ,将其作为nextResponder;
UIApplication 是响应者链的终点,它的 nextResponder指向 nil,整个responder chain 结束。
(三)总结
事件的链有两条:事件的响应链; Hit-Testing 时事件的传递链。
响应链:由离用户最近的view向系统传递。 initial view –> super view –> …–> view controller –> window –> Application –> AppDelegate
· Hit-Testing传递链:由系统向离用户最近的 view传递。 UIKit –> active app’s event queue –> window –> root view –>…–>lowest view
事件的传递是从上到下从父控件到子控件,事件的响应是从下到上,从子控件到父控件,顺着响应者链条向上传递。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章