iOS 事件传递与响应链
iOS 事件响应链
事件的传递与响应
基本概念
UIResponder
- 在 iOS 中,只有继承了 UIResponder 的对象才能接受并处理事件
UIApplication (NSObject -> UIResponder -> UIApplication)
UIViewController (NSObject -> UIResponder -> UIViewController)
UIView (NSObject -> UIResponder -> UIView)
UIWindow (NSObject -> UIResponder -> UIView -> UIWindow)
UIButton (NSObject -> UIResponder -> UIView -> UIControl -> UIButton)
- UIResponder 提供以下方法,来处理事件
1 | // UIResponder内部提供了以下方法来处理事件触摸事件 |
UITouch
当用户用一根手指触摸屏幕时,会创建一个与手指相关的 UITouch 对象
一根手指对应一个 UITouch 对象
如果两根手指同时触摸一个 view,那么 view 只会调用一次 touchesBegan:withEvent:方法,touches 参数中装着 2 个 UITouch 对象
如果这两根手指一前一后分开触摸同一个 view,那么 view 会分别调用 2 次 touchesBegan:withEvent:方法,并且每次调用时的 touches 参数中只包含一个 UITouch 对象
1 | //触摸产生时所处的窗口 |
以 UIView 为例,重写 UIResponder 方法,说明事件的处理
- 重写以下 4 个方法,处理不同的触摸事件
注意,直接在 UIViewController 的.m 文件中重写,重写的是 VC 的方法,并非 VC.view 的
1 | // touches中存放的都是UITouch对象 |
事件的产生与传递
事件的产生与传递
发生触摸事件后,系统会将该事件加入到一个由 UIApplication 管理的事件队列(先进先出)中。
UIApplication 会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件
触摸事件的传递是从父控件传递到子控件,也就是 UIApplication->window->寻找处理事件最合适的 view。
如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件
- 找到合适的视图控件后,就会调用视图控件的 touches 方法来作具体的事件处理。
寻找最合适的控件处理触摸事件
通过 hitTest:withEvent: 来找到最合适的控件,处理事件。
首先判断主窗口(keyWindow)自己是否能接受触摸事件
以下情况不能接收触摸事件
不允许交互:userInteractionEnabled = NO
隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件
透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01 为透明
判断触摸点是否在自己身上
1
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
子控件数组中从后往前遍历子控件,重复前面的两个步骤
从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行 1、2 步骤
view,比如叫做 fitView,那么会把这个事件交给这个 fitView,再遍历这个 fitView 的子控件,直至没有更合适的 view 为止。
如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的 view。
无论控件能否处理事件,只要点击了就会产生事件,区别是最终由哪个控件来处理事件。
综上,hitTest:withEvent: 的实现大致如下
1 | //Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point |
其他操作
可以通过重写 hitTest:withEvent 来修改响应事件的 view
1 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ |
响应链
responder chain (响应者链条)
在 iOS 程序中无论是最后面的 UIWindow 还是最前面的某个按钮,它们的摆放是有前后关系的,一个控件可以放到另一个控件上面或下面。
这种先后关系构成一个链条就叫“响应者链”。
也可以说,响应者链是由多个响应者对象连接起来的链条。
响应链的传递过程
- 如果当前 view 是控制器的 view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前 view 不是控制器的 view,那么父视图就是当前 view 的上一个响应者,事件就传递给它的父视图
- 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给 window 对象进行处理
- 如果 window 对象也不处理,则其将事件或消息传递给 UIApplication 对象
- 如果 UIApplication 也不能处理该事件或消息,则将其丢弃
总结
iOS 事件处理的完整流程
触摸屏幕产生触摸事件后,触摸事件会被添加到由 UIApplication 管理的事件队列中。
UIApplication 会从事件队列中取出最前面的事件,把事件传递给应用程序的主窗口(keyWindow)。
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,完成事件的传递)
最合适的 view 会调用自己的 touches 方法处理事件,touches 默认做法是把事件顺着响应者链条向上抛。(开始响应链传递)
1 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ |
如何做到一个事件多个对象处理:
通过重写 touches 相关方法和父控件的 touches 方法,来达到一个事件多个对象处理的目的。
1 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ |
参考:
史上最详细的 iOS 之事件的传递和响应机制-原理篇
文/VV 木公子(简书作者)
iOS 事件传递与响应链