iOS|iOS 响应者及响应者链
当我们点击一个 button 时,button 的响应消息机制分为两块:
- 首先在视图层次中找到能响应消息的那个视图即 button;
- 然后在找到的视图 button 中进行事件处理;
UIButton < UIControl < UIView < UIResponderUIButton 之所以能够处理事件,是因为它继承自 UIResponder。也就是说只有继承自UIResponder的类才能处理事件。
找响应者
如图,找响应者是从父 View 到子 View 过程查找。主要用到了 UIView 的
hitTest:withEvent:
以及 pointInside:withEvent:
两个方法。文章图片
原理如下:
- 当用户点击屏幕时,会产生一个触摸事件,系统会将该事件加入到一个由 UIApplication 管理的事件队列中;
- UIApplication 会从事件队列中取出最前面的事件进行分发以便处理,通常,先发送事件给应用程序的主窗口(UIWindow);
- 主窗口会调用
hitTest:withEvent:
方法在视图(UIView)层次结构中找到一个最合适的 UIView 来处理触摸事件
(hitTest:withEvent:
其实是 UIView 的一个方法,UIWindow 继承自 UIView,因此主窗口 UIWindow 也是属于视图的一种);
hitTest:withEvent:
方法处理机制:
当前 view 调用自身的 pointInside: withEvent:
方法判断触摸点是否在自己范围内:- 若
pointInside: withEvent:
方法返回 NO,则说明触摸点不在自己范围内,则当前 view 的hitTest: withEvent:
方法返回 nil,当前 view上 的所有 subview 都不做判断。有点领导的意见一票否决的味道。
- 若
pointInside: withEvent:
方法返回 YES,则说明触摸点在自己的范围内。但无法判断是否在自己身上还是在 subview 的身上。此时,遍历所有的 subviews,对每个 subview 调用 hitTest 方法。这里要注意,遍历的顺序是从当前 view 的 subviews 数组的尾部开始遍历。因此离用户最近的上层的 subview 会优先被调用 hitTest 方法。
- 一旦 hitTest 方法返回非空的 view,则被返回的 view 就是最终相应触摸事件的 view,寻找 hitTesting view 的阶段到此结束,不再遍历。
若当前 view 的所有 subviews 的 hitTest 方法都返回 nil,则当前 view 的 hitTest 方法返回 self 作为最终的 hitTesting view,处理结束。
如图:
文章图片
当用户点击ViewD所在的区域时会进行以下hit-Testing:
- 【iOS|iOS 响应者及响应者链】ViewA 的 pointInside 返回 YES,因为触摸点在其 bounds 内。遍历 ViewA 的两个 subview;
- ViewB 的 pointInside 返回 NO,因为触摸点不在其 bounds 内,ViewB 的 hitTest 方法返回 nil。而且发生一票否决,在 ViewB 上的所有 subviews 受到牵连将不再进行 hit-Testing 处理。
- ViewC 的 pointInside 返回 YES,因为触摸点在其 bounds 范围内,ViewC 的 hitTest 方法返回默认处理,也就是
return [super hitTest:point withEvent:event];
遍历 ViewC 的两个 subview;
- ViewD的 pointInside 返回 YES,因为触摸点在其 bounds 范围内,且ViewD 没有 subview,因此 hitTest 方法返回其自己。hitTesting view 找到,结束处理
2、hit-Testing 过程是从 superView 向 subView 逐级传递,也就是从层次树的根节点向叶子节点传递;
3、遇到以下设置时,view 的 pointInside 将返回NO,hitTest 方法返回 nil:
- view.isHidden=YES;
- view.alpah<=0.01;
- view.userInterfaceEnable=NO;
- control.enable=NO; (UIControl的属性)
在上一部分已经找到了响应者,这个响应者就会执行相应的 touch 系列方法,系统默认处理事件之后将不继续向下一响应者传递。我们自己可以根据需要,通过复写方法把当前事件向下一响应者进行传递。
可以复写下列方法对事件进行处理
//触摸开始,手指触碰屏幕
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//触摸结束,手指离开屏幕
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
//触摸取消(如电话接入的时候)
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
//手指移动(会调用多次)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
//3D touch 9.1之后加入的3D触摸事件
- (void)touchesEstimatedPropertiesUpdated:(NSSet *)touches
举个例子:
新建一个 Single View App,在 ViewController.m 中:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{//加上这一句,事件就可以向下一个响应者传递
[super touchesBegan:touches withEvent:event];
NSLog(@"viewController touch begin");
}
在 AppDelegate.m 中:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"appDelegate touch begin");
}
这样我们就实现了将事件向下一个响应者传递。
响应者链
下图响应者链链来自官网
文章图片
我们也可以通过代码打印响应者链:
- (IBAction)click:(id)sender {
UIResponder *res = sender;
while (res) {
NSLog(@"*************************************\n%@",res);
res = [res nextResponder];
}
}
- UIView 的 nextResponder 属性,如果有管理此 view 的 UIViewController 对象,则为此 UIViewController 对象;否则 nextResponder 即为其 superview。
- UIViewController 的 nextResponder 属性为其管理 view 的 superview.
- UIWindow 的 nextResponder 属性为 UIApplication 对象。
- UIApplication 的 nextResponder 属性为 nil。
1、如果 hit-test view 或 first responder 不处理此事件,则将事件传递给其 nextResponder 处理,若有 UIViewController 对象则传递给 UIViewController,传递给其 superView。
2、如果 view 的 viewController 也不处理事件,则 viewController 将事件传递给其管理 view 的 superView。
3、视图层级结构的顶级为 UIWindow 对象,如果 window 仍不处理此事件,传递给 UIApplication.
4、若 UIApplication 对象不处理此事件,则事件被丢弃。
了解响应者链有时候可以帮我解决一些实际问题。我举个例子,我们知道,当提供给你一个ViewController你可以很容易得到它的view,一句代码的事情:
viewWanted = someViewController.view;
但如果反过来呢?当给你一个view,让你找到其所在的ViewController呢?这时候响应者链可以帮上忙了,代码如下:
@implementation UIView (FindController)
-(UIViewController*)parentController{
UIResponder *responder = [self nextResponder];
while (responder) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController*)responder;
}
responder = [responder nextResponder];
}
return nil;
}
@end
放一张完整的图来理解下iOS触摸事件的流动:
文章图片
参考资料:
官方文档
https://www.cnblogs.com/Quains/p/3369132.html
https://www.cnblogs.com/wengzilin/p/4720550.html
http://shellhue.github.io/2017/03/04/FlowOfUITouch/
推荐阅读
- JS中的各种宽高度定义及其应用
- 2020-04-07vue中Axios的封装和API接口的管理
- 参保人员因患病来不及到指定的医疗机构就医,能否报销医疗费用()
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- iOS中的Block
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- 经历了人生,才知道人生的艰难!及精彩!
- 罗塞塔石碑的意义(古埃及文字的起源,圣书体文字是如何被破解的)
- 记录iOS生成分享图片的一些问题,根据UIView生成固定尺寸的分享图片
- 以太坊中的计量单位及相互转换