iOS 开发-事件传递响应链

当我们点击屏幕时,iPhone OS 获取到了用户"单击"这一行为,操作系统把包含这些点击事件的信息封装成 UITouchUIEvent 形成的实例,然后找到当前运行的程序,逐级找到能够响应这个事件的对象,知道没有响应者响应。这一寻找的过程称为响应链

事件响应链

响应者

在 iOS 中,能够响应事件的对象都是 UIResponder 的子类对象。
UIResponder 提供了用户点击的四个回调方法,分别对应点击、移动、点击结束和取消点击,其中取消点击只有在程序强制退出或来电视才会调用。


UIResponder 点击事件

响应链传递

系统时怎么通过用户点击的位置找到处理点击事件的 view ?
上文说过系统通过不断查找下一个响应者来响应点击事件,而所有的课加护空间都是 UIResponder 直接或者间接的子类,那么我们是否可以在这个类的头文件中找到关键的属性呢?正好存在这么一个方法 - (nullable UIResponder*)nextResponder;,通过方法名不难获取当前 view 的下一个响应者,那么我们重写 touchesBegan方法, 逐级获取下一个响应者,直到没有响应者位置。
相关代码如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UIResponder * next = [self nextResponder];
    NSMutableString * prefix = @"".mutableCopy;
 
    while (next != nil) {
        NSLog(@"%@%@", prefix, [next class]);
        [prefix appendString: @"--"];
        next = [next nextResponder];
    }    
}

运行结果:

AView
--UIView
----ViewController
------UIWindow
--------UIApplication
----------AppDelegate

虽然结果非常有层次,但是从系统逐级查找响应者的角度上来说,这个输出的顺序是刚好相反的。为什么会出现这种问题呢?我们可以看到输出中存在一个ViewController类,说明UIViewController也是UIResponder的子类。但是我们可以发现,controller是一个view的管理者,即便它是响应链的成员之一,但是按照逻辑来说,控制器不应该是系统查找对象之一,通过nextResponder方法查找的这个思路是不正确的。

后来在 UIView 中发现这两个方法,分别返回 UIview 和 BooL 类型的方法:

UIView 两个方法

一个方法是返回响应点击事件的对象,
另一个是根据点击坐标返回事件是否发生在本视图内。

响应者栈:
所有响应者都是在查找中返回可响应点击的视图,因此可以推测,UIApplication 对象维护这自己的一个响应者栈,当 pointInside:withEvent: 返回 YES 时候,响应者入栈。


响应者栈

栈顶作为最先响应的对象,如果 AVView 不处理事件,那么出栈,移交给 UIView,依次下去,知道事件得到了处理或者到达 APPDelegate 后依旧未响应,事件被摒弃为止。

推荐阅读更多精彩内容

  • 当我们在使用微信等工具,点击扫一扫,就能打开二维码扫描视图。在我们点击屏幕的时候,iphone OS获取到了用户进...
    lbfly_boy阅读 90评论 0 0
  • 序言 当我们在使用微信等工具,点击扫一扫,就能打开二维码扫描视图。在我们点击屏幕的时候,iphone OS获取到了...
    蓬莱侠士阅读 123评论 1 0
  • 本文地址 当用户的手指在屏幕上的某一点按下时,屏幕接收到点击信号将点击位置转换成具体坐标,然后本次点击被包装成一个...
    sindri的小巢阅读 5,879评论 16 72
  • 序言面试经常问道的一个问题:在一个app中间有一个button,在你手触摸屏幕点击后,到这个button收到点击事...
    Little_Shaun阅读 151评论 0 0
  • 存款第21天:2017年6月24日 尊荣国成牧师,2009年到2016年,我们有七年时间的连接,谢谢你常代表母堂表...
    马慧Sarah阅读 260评论 5 9