概述
iOS 响应者链(Responder Chain) 是支持App界面交互的重要基础, 点击, 滑动, 旋转, 摇晃等都离不开其背后的响应者链.
响应者 (Responder)
当我们触控手机屏幕时, 系统便会将这一操作封装成一个UIEvent 放到事件队列中, 然后Application 从事件队列中取出这个事件, 按着需要找到去响应这个事件的最佳视图就是 Responder.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {}
首先从UIWindow开始调用hitTest, 然后来到RootView的hitTest & pointInside. 继续遍地rootView子视图(从后添加的view开始), 目标视图的superView hitTest 返回 superView, pointInside 返回 YES; 同级非目标视图hitTest 返回 nil, pointInside 返回 NO; 目标视图hitTest 返回 目标视图, pointInside 返回 YES, 最终找到响应视图!
- 结论
- 寻找事件的最佳响应者是通过 hitTest & pointInside 完成的;
- hitTest 调用顺序是从UIWindow开始, 对视图每个子视图依次调用, 子视图的调用顺序是从后往前, 即后添加的先调用
3.遍历子视图知道找到响应视图, 然后逐级返回最终到UIWindow返回此视图.
处理者
在上面我们找到了点击事件的响应者, 但是我们未添加响应的点击处理逻辑(tapGestureRecognizer), 所以目标视图不会处理.
能够处理UI事件都是继承UIResponder的子类对象, UIResponder 主要有以下四个方法来处理事件:
- (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;
如果某个Responder 没有处理事件, 事件会被传递. UIResponder 对象都有一个nextResponder 属性, 用于返回响应者链(Responder chain)中的下一个事件处理者, 如果所有Responder都不处理事件, 那么事件将会被丢弃.
// 打印响应者链
UIResponder *nextResponder = self.view3.nextResponder;
NSMutableString *pre = [NSMutableString stringWithString:@"--"];
NSLog(@"View3");
while (nextResponder) {
NSLog(@"%@%@", pre, NSStringFromClass([nextResponder class]));
[pre appendString:@"--"];
nextResponder = nextResponder.nextResponder;
}
下一个响应者规则:
如果有父视图则nextResponder为父视图, 如果是控制器根视图则指向控制器, 如果控制器在导航控制器中则指向导航控制器, 如果是根控制器则指向UIWindow, UIWindow的nextResponder指向UIApplication, 最后指向APPdelegate, 它们实现这一套是通过重写nextResponder实现的.
- 结论
1.找到最合适的响应视图后, 事件会从此视图开始沿着响应者链nextResponder传递, 直到处理事件的视图, 如果没有响应者处理事件则丢弃.
2.如果视图有父视图则nextResponder指向父视图, 如果是根视图则指向控制器, 最终指向APPdelegate, 他们都是通过重写nextResponder 来实现的.
无法响应的情况
不会调用hitTest的情况
- alpha == 0,
- 子视图超出父视图,
- userInteractionEnabled == NO
- hidden == YES
Runloop 处理点击事件
Runloop 是app 运行的基础机制, 它一直处于接收消息-> 等待-> 处理的循环中, 当没有事件处理时会处于休眠状态, 等待下一个事件到来的唤醒去处理事件.
当一个触摸事件发生后, 首先是由IOKit.framwork 生成一个IOHIDEvent事件并由Springboard接收, 然后springboard会通过mach port 将事件转发给 app 进程, 然后触发app注册在runloop中的source1来处理事件, source1 会触发 __IOHIDEventSystemClientQueueCallBack回调, 回调后又触发 source0, 在后面就是 UIApplication从事件队列中取出事件分发.