iOS 响应者及响应者链

当我们点击一个 button 时,button 的响应消息机制分为两块:

  • 首先在视图层次中找到能响应消息的那个视图即 button;

  • 然后在找到的视图 button 中进行事件处理;

UIButton 继承关系:

UIButton < UIControl < UIView < UIResponder

UIButton 之所以能够处理事件,是因为它继承自 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:

  • 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 找到,结束处理

需要注意的地方:

1、hitTest 方法调用 pointInside 方法;

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<UITouch *> *)touches withEvent:(UIEvent *)event
//触摸结束,手指离开屏幕
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//触摸取消(如电话接入的时候)
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//手指移动(会调用多次)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//3D touch 9.1之后加入的3D触摸事件
- (void)touchesEstimatedPropertiesUpdated:(NSSet *)touches

举个例子:

新建一个 Single View App,在 ViewController.m 中:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //加上这一句,事件就可以向下一个响应者传递
    [super touchesBegan:touches withEvent:event];
    
    NSLog(@"viewController touch begin");
}

在 AppDelegate.m 中:

-(void)touchesBegan:(NSSet<UITouch *> *)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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335