iOS程序的响应链是一个非常有意思的事情,明确了响应链的调用顺序,那么就可以在响应链中操作响应的对象,这样有助于实现一些特殊的需求,比如控件重写,自定义控件的响应顺序。
这里先明确几个概念
UIEvent-> 是事件本身,事件中包含三种状态的事件,Touch屏幕触摸事件、Motion感应事件(例如摇晃)、Remote远程事件(其他比如手表、手环之类)
UIResponder->响应者,专门来响应用户的事件及对事件的处理
Hit-Testing -> 寻找最佳响应者
Responder chain -> 响应链本身
UIControl -> 类似于手势,比手势更加的精简,继承自uiview,基类来自于UIResponder
了解这几个概念之后,顺着代码来看看响应者链的顺序。
在这里写了一个HView继承自UIView,重写了三个方法,重要的是 overridefunchitTest(_point:CGPoint,withevent:UIEvent?) ->UIView?和 overridefuncpoint(insidepoint:CGPoint,withevent:UIEvent?) ->Bool这两个方法
需要记得的是这两个方法是组合起来使用的
打好断点,看一下调用的顺序
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
hit-Testing`HView.hitTest(point=(x = 75.666666666666657, y = 76.333333333333314), event=0x0000600002f700c0, self=0x00007fc974106380) at HView.swift:14:15
[UIView(Geometry) _hitTest:withEvent:windowServerHitTestWindow:] + 87
-[UIDropShadowView hitTest:withEvent:] + 327
-[UIView(Geometry) _hitTest:withEvent:windowServerHitTestWindow:] + 87
-[UITransitionView hitTest:withEvent:] + 44
-[UIView(Geometry) hitTest:withEvent:]_block_invoke + 121
[UIWindow _hitTestToPoint:forEvent:windowServerHitTestWindow:]_block_invoke + 84
-[UIWindowScene _topVisibleWindowPassingTest:] + 482
[UIWindow _hitTestToPoint:forEvent:windowServerHitTestWindow:] + 238
hit-Testing`static UIApplicationDelegate.main() at :0
AppDelegate.$main(self=hit_Testing.AppDelegate) at AppDelegate.swift:10:1
hit-Testing`main at :0
这里只保留了每一个阶段的调用,这样的顺序一目了然
HView -> UIView->UIDropShadowView->UIView->UITransitionView->UIView-> UIWindow->UIWindowScene->UIWindow->AppDelegate->main
再度精简HView -> UIView->UIDropShadowView->UITransitionView-> UIWindow->UIWindowScene->UIWindow->AppDelegate->main
通过打印获得了一个完整的调用链,其实最终的入口还是在main的代理AppDelegate中,当点击一个区域的时候,从AppDelegate中逐级的响应,直到找到最终的最佳响应者。
了解了响应者链的顺序,这里还有几个规则需要注意,响应者链从这个字面意思就可以知道这是一个链式的存在,UIView和UIViewController都是继承自UIResponder,UIResponder是链式存储,那么它自身必然是有一个指针指向下一个对象的。
1.UIView如果是ViewController的rootView,则UIView的nextResponder指向该ViewController,否则UIView的nextResponder指向的是UIView的superView
2.ViewController如果是UIWindow的rootViewControoler,则ViewController的nextResponder指向该UIWindow,否则,ViewController的nextResponder指向自身的前一个ViewController
3 UIWindow的nextResponder是指向AppDelegate的。
对于手势需要注意的点:
手势的优先级高于UIResponder,设置了手势之后,手势的四个阶段分别是touchesBegan,Moved,Ended,Cancelled,对于手势的响应最好是在Ended中去操作,因为Ended之后,才会去真正的处理事件,处理完事件之后系统再回调Cancelled方法。在Ended处理,减少从touchesBegan到Ended的传递,这样也是细微的优化。