iOS开发——响应链那些事

一、响应链相关的两个核心函数

a、返回当前可以响应的此次操作的视图

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

b、返回当前碰触的屏幕坐标是否在当前视图中

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

二、响应链逻辑实验相关代码

自定义Button1、Button2、Button3 继承自UIButton,重写hitTest、pointInside两个函数增加log

以Button1举例:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    NSLog(@"-----hitTest star Button1-----");
    UIView* view = [super hitTest:point withEvent:event];
    NSLog(@"-----hitTest end Button1----- %@",view);
    return view;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    BOOL result = [super pointInside:point withEvent:event];
    NSLog(@"-----pointInside Button1----- %@",@(result));
    return result;
}

实验一、将三个button实例化,平行添加到VC视图中,互相不重叠

    Button1 *btn1 = [[Button1 alloc]initWithFrame:CGRectMake(100, 100, 50, 50)];
    [btn1 setTitle:@"btn1" forState:UIControlStateNormal];
    btn1.backgroundColor = [UIColor redColor];
    
    Button2 *btn2 = [[Button2 alloc]initWithFrame:CGRectMake(160, 100, 50, 50)];
    [btn2 setTitle:@"btn2" forState:UIControlStateNormal];
    btn2.backgroundColor = [UIColor yellowColor];
    
    Button3 *btn3 = [[Button3 alloc]initWithFrame:CGRectMake(220, 100, 50, 50)];
    [btn3 setTitle:@"btn3" forState:UIControlStateNormal];
    btn3.backgroundColor = [UIColor blueColor];
    
    [self.view addSubview:btn1];
    [self.view addSubview:btn2];
    [self.view addSubview:btn3];

操作一:此时点击空白处,打印log如下:

2020-04-05 14:35:28.522130+0800 hitTestDemo[1815:64004] -----hitTest star Button3-----
2020-04-05 14:35:28.522224+0800 hitTestDemo[1815:64004] -----pointInside Button3----- 0
2020-04-05 14:35:28.522281+0800 hitTestDemo[1815:64004] -----hitTest end Button3----- (null)
2020-04-05 14:35:28.522333+0800 hitTestDemo[1815:64004] -----hitTest star Button2-----
2020-04-05 14:35:28.522382+0800 hitTestDemo[1815:64004] -----pointInside Button2----- 0
2020-04-05 14:35:28.522449+0800 hitTestDemo[1815:64004] -----hitTest end Button2----- (null)
2020-04-05 14:35:28.522512+0800 hitTestDemo[1815:64004] -----hitTest star Button1-----
2020-04-05 14:35:28.522613+0800 hitTestDemo[1815:64004] -----pointInside Button1----- 0
2020-04-05 14:35:28.522662+0800 hitTestDemo[1815:64004] -----hitTest end Button1----- (null)

//执行第二遍
2020-04-05 14:35:28.522748+0800 hitTestDemo[1815:64004] -----hitTest star Button3-----
2020-04-05 14:35:28.522793+0800 hitTestDemo[1815:64004] -----pointInside Button3----- 0
2020-04-05 14:35:28.522877+0800 hitTestDemo[1815:64004] -----hitTest end Button3----- (null)
2020-04-05 14:35:28.523017+0800 hitTestDemo[1815:64004] -----hitTest star Button2-----
2020-04-05 14:35:28.542243+0800 hitTestDemo[1815:64004] -----pointInside Button2----- 0
2020-04-05 14:35:28.542305+0800 hitTestDemo[1815:64004] -----hitTest end Button2----- (null)
2020-04-05 14:35:28.542364+0800 hitTestDemo[1815:64004] -----hitTest star Button1-----
2020-04-05 14:35:28.542424+0800 hitTestDemo[1815:64004] -----pointInside Button1----- 0
2020-04-05 14:35:28.542466+0800 hitTestDemo[1815:64004] -----hitTest end Button1----- (null)

根据log可见,主视图view的hitTest中会遍历所有子视图,并执行子视图的hitTest方法,遍历顺序是subviews数组的倒序。

点击空白视图的时候,Button1、Button2、Button3 分别执行中hitTest,在hitTest中调用pointInside,均返回了0,说明当前碰触的屏幕坐标均不在三个视图中;hitTest返回的值都是null,说明当前操作,这三个视图均无法提供可以响应此次操作的视图。

操作二:此时点击红色按钮Button1,打印log如下:

2020-04-05 14:46:34.098658+0800 hitTestDemo[1899:72303] -----hitTest star Button3-----
2020-04-05 14:46:34.098857+0800 hitTestDemo[1899:72303] -----pointInside Button3----- 0
2020-04-05 14:46:34.098970+0800 hitTestDemo[1899:72303] -----hitTest end Button3----- (null)
2020-04-05 14:46:34.099065+0800 hitTestDemo[1899:72303] -----hitTest star Button2-----
2020-04-05 14:46:34.099160+0800 hitTestDemo[1899:72303] -----pointInside Button2----- 0
2020-04-05 14:46:34.099247+0800 hitTestDemo[1899:72303] -----hitTest end Button2----- (null)
2020-04-05 14:46:34.099315+0800 hitTestDemo[1899:72303] -----hitTest star Button1-----
2020-04-05 14:46:34.099393+0800 hitTestDemo[1899:72303] -----pointInside Button1----- 1
2020-04-05 14:46:34.099888+0800 hitTestDemo[1899:72303] -----hitTest end Button1----- <Button1: 0x7fee0ea07c00; baseClass = UIButton; frame = (100 100; 50 50); opaque = NO; layer = <CALayer: 0x600000fad6a0>>

//执行第二遍
2020-04-05 14:46:34.100059+0800 hitTestDemo[1899:72303] -----hitTest star Button3-----
2020-04-05 14:46:34.100147+0800 hitTestDemo[1899:72303] -----pointInside Button3----- 0
2020-04-05 14:46:34.100229+0800 hitTestDemo[1899:72303] -----hitTest end Button3----- (null)
2020-04-05 14:46:34.100310+0800 hitTestDemo[1899:72303] -----hitTest star Button2-----
2020-04-05 14:46:34.100397+0800 hitTestDemo[1899:72303] -----pointInside Button2----- 0
2020-04-05 14:46:34.100467+0800 hitTestDemo[1899:72303] -----hitTest end Button2----- (null)
2020-04-05 14:46:34.101958+0800 hitTestDemo[1899:72303] -----hitTest star Button1-----
2020-04-05 14:46:34.102017+0800 hitTestDemo[1899:72303] -----pointInside Button1----- 1
2020-04-05 14:46:34.102108+0800 hitTestDemo[1899:72303] -----hitTest end Button1----- <Button1: 0x7fee0ea07c00; baseClass = UIButton; frame = (100 100; 50 50); opaque = NO; layer = <CALayer: 0x600000fad6a0>>

点击Button1视图的时候,仍然是从Button3进行检测,直到检测到Button1时,pointInside返回的值为1,说明当前碰触的屏幕坐标在Button1视图中;Button1的hitTest返回的值是Button1实例,说明当前操作可以被Button1实例响应

实验二、Button1添加到主视图上,Button2和Button3平行添加到Button1上,Button2和Button3是Button1的子视图

    Button1 *btn1 = [[Button1 alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
    [btn1 setTitle:@"btn1" forState:UIControlStateNormal];
    btn1.userInteractionEnabled = false;
    btn1.backgroundColor = [UIColor redColor];
    
    Button2 *btn2 = [[Button2 alloc]initWithFrame:CGRectMake(30, 30, 50, 50)];
    [btn2 setTitle:@"btn2" forState:UIControlStateNormal];
    btn2.backgroundColor = [UIColor yellowColor];
    
    Button3 *btn3 = [[Button3 alloc]initWithFrame:CGRectMake(110, 30, 50, 50)];
    [btn3 setTitle:@"btn3" forState:UIControlStateNormal];
    btn3.backgroundColor = [UIColor blueColor];
    
    [self.view addSubview:btn1];
    [btn1 addSubview:btn2];
    [btn1 addSubview:btn3];

操作一:此时点击空白处,打印log如下:

2020-04-05 14:55:41.633552+0800 hitTestDemo[1922:76463] -----hitTest star Button1-----
2020-04-05 14:55:41.633929+0800 hitTestDemo[1922:76463] -----hitTest end Button1----- (null)

//执行第二遍
2020-04-05 14:55:41.634152+0800 hitTestDemo[1922:76463] -----hitTest star Button1-----
2020-04-05 14:55:41.634229+0800 hitTestDemo[1922:76463] -----hitTest end Button1----- (null)

当前主视图的子视图只有Button1,Button1无法响应

操作二:此时点击Button1(不被Button2、Button3覆盖的红色区域),打印log如下:

2020-04-05 14:57:26.486880+0800 hitTestDemo[1941:78733] -----hitTest star Button1-----
2020-04-05 14:57:26.486976+0800 hitTestDemo[1941:78733] -----pointInside Button1----- 1
2020-04-05 14:57:26.487081+0800 hitTestDemo[1941:78733] -----hitTest star Button3-----
2020-04-05 14:57:26.487153+0800 hitTestDemo[1941:78733] -----pointInside Button3----- 0
2020-04-05 14:57:26.487211+0800 hitTestDemo[1941:78733] -----hitTest end Button3----- (null)
2020-04-05 14:57:26.487274+0800 hitTestDemo[1941:78733] -----hitTest star Button2-----
2020-04-05 14:57:26.487350+0800 hitTestDemo[1941:78733] -----pointInside Button2----- 0
2020-04-05 14:57:26.487401+0800 hitTestDemo[1941:78733] -----hitTest end Button2----- (null)
2020-04-05 14:57:26.487777+0800 hitTestDemo[1941:78733] -----hitTest end Button1----- <Button1: 0x7f82d08082d0; baseClass = UIButton; frame = (100 100; 200 200); opaque = NO; layer = <CALayer: 0x600001b3dac0>>

//执行第二遍
2020-04-05 14:57:26.487885+0800 hitTestDemo[1941:78733] -----hitTest star Button1-----
2020-04-05 14:57:26.487955+0800 hitTestDemo[1941:78733] -----pointInside Button1----- 1
2020-04-05 14:57:26.488003+0800 hitTestDemo[1941:78733] -----hitTest star Button3-----
2020-04-05 14:57:26.503970+0800 hitTestDemo[1941:78733] -----pointInside Button3----- 0
2020-04-05 14:57:26.504106+0800 hitTestDemo[1941:78733] -----hitTest end Button3----- (null)
2020-04-05 14:57:26.504199+0800 hitTestDemo[1941:78733] -----hitTest star Button2-----
2020-04-05 14:57:26.504263+0800 hitTestDemo[1941:78733] -----pointInside Button2----- 0
2020-04-05 14:57:26.504329+0800 hitTestDemo[1941:78733] -----hitTest end Button2----- (null)
2020-04-05 14:57:26.504457+0800 hitTestDemo[1941:78733] -----hitTest end Button1----- <Button1: 0x7f82d08082d0; baseClass = UIButton; frame = (100 100; 200 200); opaque = NO; layer = <CALayer: 0x600001b3dac0>>

由log可见,在Button1的hitTest中先调pointInside,检测当前操作坐标是在Button1范围内,然后分别倒序遍历Button1的子视图[Button2,Button3],分别执行hitTest方法,在hitTest中调研pointInside,Button2,Button3的pointInside均返回0,hitTest均返回null,可见此次操作均无法在Button2,Button3中得到响应,最后Button1实例将自己提供为此次操作的相应View;

操作三:此时点击Button2,打印log如下:

2020-04-05 15:11:49.864895+0800 hitTestDemo[1941:78733] -----hitTest star Button1-----
2020-04-05 15:11:49.865002+0800 hitTestDemo[1941:78733] -----pointInside Button1----- 1
2020-04-05 15:11:49.865078+0800 hitTestDemo[1941:78733] -----hitTest star Button3-----
2020-04-05 15:11:49.865134+0800 hitTestDemo[1941:78733] -----pointInside Button3----- 0
2020-04-05 15:11:49.865193+0800 hitTestDemo[1941:78733] -----hitTest end Button3----- (null)
2020-04-05 15:11:49.865240+0800 hitTestDemo[1941:78733] -----hitTest star Button2-----
2020-04-05 15:11:49.865307+0800 hitTestDemo[1941:78733] -----pointInside Button2----- 1
2020-04-05 15:11:49.865489+0800 hitTestDemo[1941:78733] -----hitTest end Button2----- <Button2: 0x7f82d0808a10; baseClass = UIButton; frame = (30 30; 50 50); opaque = NO; layer = <CALayer: 0x600001b3db00>>
2020-04-05 15:11:49.865579+0800 hitTestDemo[1941:78733] -----hitTest end Button1----- <Button2: 0x7f82d0808a10; baseClass = UIButton; frame = (30 30; 50 50); opaque = NO; layer = <CALayer: 0x600001b3db00>>

//执行第二遍
2020-04-05 15:11:49.865675+0800 hitTestDemo[1941:78733] -----hitTest star Button1-----
2020-04-05 15:11:49.881797+0800 hitTestDemo[1941:78733] -----pointInside Button1----- 1
2020-04-05 15:11:49.881889+0800 hitTestDemo[1941:78733] -----hitTest star Button3-----
2020-04-05 15:11:49.881958+0800 hitTestDemo[1941:78733] -----pointInside Button3----- 0
2020-04-05 15:11:49.882005+0800 hitTestDemo[1941:78733] -----hitTest end Button3----- (null)
2020-04-05 15:11:49.882046+0800 hitTestDemo[1941:78733] -----hitTest star Button2-----
2020-04-05 15:11:49.882114+0800 hitTestDemo[1941:78733] -----pointInside Button2----- 1
2020-04-05 15:11:49.882201+0800 hitTestDemo[1941:78733] -----hitTest end Button2----- <Button2: 0x7f82d0808a10; baseClass = UIButton; frame = (30 30; 50 50); opaque = NO; layer = <CALayer: 0x600001b3db00>>
2020-04-05 15:11:49.882284+0800 hitTestDemo[1941:78733] -----hitTest end Button1----- <Button2: 0x7f82d0808a10; baseClass = UIButton; frame = (30 30; 50 50); opaque = NO; layer = <CALayer: 0x600001b3db00>>

由log可见,仍然是,在Button1的hitTest中先调pointInside,检测当前操作坐标是在Button1范围内,然后分别倒序遍历Button1的子视图[Button2,Button3],分别执行hitTest方法,在hitTest中调研pointInside,Button3的pointInside均返回0,hitTest返回null,Button2的pointInside均返回1,hitTest返回Button2实例,可见此次操作Button2实例将自己提供为此次操作的相应View;

附加实验:将Button2的userInteractionEnabled 设置为NO,重复操作三,log如下:

2020-04-05 15:18:31.830530+0800 hitTestDemo[1993:88543] -----hitTest star Button1-----
2020-04-05 15:18:31.830673+0800 hitTestDemo[1993:88543] -----pointInside Button1----- 1
2020-04-05 15:18:31.830767+0800 hitTestDemo[1993:88543] -----hitTest star Button3-----
2020-04-05 15:18:31.830835+0800 hitTestDemo[1993:88543] -----pointInside Button3----- 0
2020-04-05 15:18:31.830923+0800 hitTestDemo[1993:88543] -----hitTest end Button3----- (null)
2020-04-05 15:18:31.831013+0800 hitTestDemo[1993:88543] -----hitTest star Button2-----
2020-04-05 15:18:31.831106+0800 hitTestDemo[1993:88543] -----hitTest end Button2----- (null)
2020-04-05 15:18:31.831710+0800 hitTestDemo[1993:88543] -----hitTest end Button1----- <Button1: 0x7f869ed046b0; baseClass = UIButton; frame = (100 100; 200 200); opaque = NO; layer = <CALayer: 0x600002bba380>>

//执行第二遍
2020-04-05 15:18:31.831881+0800 hitTestDemo[1993:88543] -----hitTest star Button1-----
2020-04-05 15:18:31.831960+0800 hitTestDemo[1993:88543] -----pointInside Button1----- 1
2020-04-05 15:18:31.832045+0800 hitTestDemo[1993:88543] -----hitTest star Button3-----
2020-04-05 15:18:31.832130+0800 hitTestDemo[1993:88543] -----pointInside Button3----- 0
2020-04-05 15:18:31.832204+0800 hitTestDemo[1993:88543] -----hitTest end Button3----- (null)
2020-04-05 15:18:31.832261+0800 hitTestDemo[1993:88543] -----hitTest star Button2-----
2020-04-05 15:18:31.833534+0800 hitTestDemo[1993:88543] -----hitTest end Button2----- (null)
2020-04-05 15:18:31.833620+0800 hitTestDemo[1993:88543] -----hitTest end Button1----- <Button1: 0x7f869ed046b0; baseClass = UIButton; frame = (100 100; 200 200); opaque = NO; layer = <CALayer: 0x600002bba380>>

log与操作三类似,唯一不一致的是最后Button2的pointInside均返回1,hitTest返回null,Button1的hitTest返回Button1自身实例,可见userInteractionEnabled的View无法被当做响应者的view提供,据测试(透明度小于0.01或者被隐藏的view)也无法被当做响应者的view提供。

三、响应链逻辑总结

根据实验逻辑,模拟下 hitTest 方法的大概实现:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 如果交互未打开,或者透明度小于0.01 或者 视图被隐藏
    if (!self.userInteractionEnabled || self.alpha < 0.01 || self.isHidden) {
        return nil;
    }
    // 如果 touch 的point 在 self 的bounds 内
    if ([self pointInside:point withEvent:event]) {
        for ( int i = (int)self.subviews.count - 1; i >=0; i--) {
            //倒序遍历
            UIView* subView = self.subviews[i];
            //进行坐标转化
            CGPoint coverPoint = [subView convertPoint:point fromView:self];
            // 调用子视图的 hitTest 重复上面的步骤。找到了,返回hitTest view ,没找到返回有自身处理
            UIView *hitTestView = [subView hitTest:coverPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

四、userInteractionEnabled原理及及应用

userInteractionEnabled是视图的一个属性,标识该视图是否可以响应用户操作。当userInteractionEnabled为NO,则该视图永远无法成为操作的响应者,hitTest函数的返回值永远是null。只有userInteractionEnabled为YES,该视图才有成为响应者的机会。

在IOS中UI控件的userInteractionEnabled默认值并不相同:

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

推荐阅读更多精彩内容