iOS响应者链、事件的传递

1、响应链的传递

Responder一点也不神秘————iOS用户响应者链完全剖析(建议全看)
看完上面一篇应该能完全熟悉了响应链的传递,自己可以打印一下响应链看看,代码如下:

- (IBAction)click:(id)sender {
    UIResponder *res = sender;
    
    while (res) {
        NSLog(@"*************************************\n%@",res);
        res = [res nextResponder];
    }
}

2、Hit-Test 机制

当用户触摸(Touch)屏幕进行交互时,系统首先要找到响应者(Responder)。系统检测到手指触摸(Touch)操作时,将Touch 以UIEvent的方式加入UIApplication事件队列中。UIApplication从事件队列中取出最新的触摸事件进行分发传递到UIWindow进行处理。UIWindow 会通过hitTest:withEvent:方法寻找触碰点所在的视图,这个过程称之为hit-test view。
hitTest 的顺序如下

UIApplication -> UIWindow -> Root View -> ··· -> subview

在顶级视图(Root View)上调用pointInside:withEvent:方法判断触摸点是否在当前视图内;

如果返回NO,那么hitTest:withEvent:返回nil;

如果返回YES,那么它会向当前视图的所有子视图发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕。

如果有subview的hitTest:withEvent:返回非空对象则A返回此对象,处理结束(注意这个过程,子视图也是根据pointInside:withEvent:的返回值来确定是返回空还是当前子视图对象的。并且这个过程中如果子视图的hidden=YES、userInteractionEnabled=NO或者alpha小于0.1都会并忽略);

如果所有subview遍历结束仍然没有返回非空对象,则hitTest:withEvent:返回self;

系统就是这样通过hit test找到触碰到的视图(Initial View)进行响应。

如果还不清楚Hit-Test 机制,看更加清晰的Hit-Test 机制(建议还不清楚的看)

Paste_Image.png

3、手势的原理及与touches系列的关系,具体的可以看iOS触摸事件传递响应之被忽视的手势识别器工作原理(建议不看也没关系,结论在下面了。)

简而言之,就是下面这幅图了。触摸事件会优先分发给附在view的手势,在这段延迟的期间,如果手势被识别,那么view的touches系列将被立刻取消,如果没有被识别,那么会继续我们所熟知的touches系列流程。


Paste_Image.png

4、实际开发中常见的相关问题

在实际开发中,经常会遇到视图没有响应的情况,特别是新手会经常搞不清楚状况。

一下是视图没有响应的几个情况:

1.userInteractionEnabled=NO;

2.hidden=YES;

3.alpha=0~0.01;

4.没有实现touchesBegan:withEvent:方法,直接执行touchesMove:withEvent:等方法;

5.目标视图点击区域不在父视图的Frame上 (superView背景色为clear Color的时候经常会忽略这个问题)。

5、手势代理

ios手势识别代理,看这个基本上就够了。引用文章中的一段话,如下:

  • 当时做项目时这个主控制器就是RootViewController,虽然用的是ScrollView但也没考虑到导航栏的手势返回的问题 ,现在做小区宝3.0的闪购订单,用之前的就有问题了。导航栏的返回手势用不了,根据响应者链和响应事件,手势被ScrollView识别了,就到不了导航的手势识别,所以导致无法手势返回。

我也曾经处理过这样的问题,不过我那时候是带有QQ的侧滑功能,主控制器用的View是ScrollView,导致不能侧滑。但是处理的方法都是一样的,自定义的ScrollView的代码重写gestureRecognizerShouldBegin方法如下,我是手势方向向右并且x轴起点小于60px的,让ScrollView的手势失效。这样就不会截获对应的事件了。但是其实看完上面,还有更简单的方法,就是让ScrollView的手势共存,但是这样可能会带来一些其它的问题。shouldRecognizeSimultaneouslyWithGestureRecognizer设置为true,不过应该要判断手势为UIScreenEdgePanGestureRecognizer时才return true,这样就可以了。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
    CGPoint location = [gestureRecognizer locationInView:self];
    
    NSLog(@"velocity.x:%f----location.x:%d",velocity.x,(int)location.x%(int)[UIScreen mainScreen].bounds.size.width);
    if (velocity.x > 0.0f&&(int)location.x%(int)[UIScreen mainScreen].bounds.size.width<60) {
        return NO;
    }
    return YES;
} 

案例分析

案例一

下面这种做法,除非你很熟悉,否则不要这么干。因为 [super touchesBegan:touches withEvent:event];会执行原来默认的操作,如果按钮本来就没有添加对应的事件。那么[[self nextResponder] touchesBegan:touches withEvent:event];和[super touchesBegan:touches withEvent:event];将会向下一响应者发送两次事件。

-(void)touchesBegan:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event{
    if (self.enableNextResponder) {
        [[self nextResponder] touchesBegan:touches withEvent:event];
        [super touchesBegan:touches withEvent:event];
    }
}

案例二

Window
  -ViewA(能响应)
    -ButtonA
    -ViewB(不响应)

假设ViewB完全覆盖在ButtonA上,结果是:
ViewA能触发
Button没反应
ViewB没反应

简单来说,ViewB能阻隔ButtonA的响应,但是不能阻隔ViewA的响应。假设ViewB是个遮罩,那么并不能阻隔ViewA的事件触发。假设需要阻隔ViewA的相应,那么可以在ViewB添加空事件的相应,从而达到ViewB(遮罩)阻隔ViewA(如游戏层)的事件相应。

案例三

一个按钮添加了点击事件到底发生了什么事儿。
我们有时候需要使用到一些特殊的情况,比如:
1、A包含B,AB都响应事件。
对于普通View,根据响应链,让B作为第一个响应者处理,然后B根据nextResponder传递触摸事件。
针对手势做分析:
手势不会走view的touches系列方法,但有自己的一系列touches方法,不过没有暴露出来。但是shouldRecognizeSimultaneouslyWithGestureRecognizer也可以做到。

针对UIButton的分析:
UIButton addTarget分析,addTarget是UIControl的方法,其实addTarget的方法原理是,UIControl对touches的触摸事件的封装。[super touchesBegan:touches withEvent:event];包括了对
事件的封装处理,如果重新了[super touchesBegan:touches withEvent:event];,并且里面什么都不实现,那么当前UIButton添加的addTarget所绑定的所有事件都不会触发。因为覆盖了父类UIControl的封装方法。
如果我想一个按钮的事件触发,并且它的下一响应者也能触发相应的事件。那么该怎么处理呢?
我们在按钮上处理,重写touchesBegan系列的方法,那么根据上面所说,必须要调用super的方法,并且主动像下一响应者[self nextResponder]发送touchesBegan系列的方法。

2、A包含B、C,C在B的上面,但是想让B接收事件,C不接收事件
这种可以这么处理,自定义C的View,重写hitTest:withEvent方法,返回nil,这样自定义C的View及其子类都不会拦截事件。这样B就可以顺利处理事件。
还可以把C的userInteractionEnabled设置为NO

3、A是B、C的父视图,C在B的上面,这时候,CB都处理事件。这样到底行不行?根据响应链,这样应该是不靠谱的了。在C的touches方法中调用C的touches方法,然后重写B的touches方法,但是这样怪怪的。有什么高招也请多多指教。貌似也没有这样的必要。

最后还发现了一篇一步到位的iOS响应者链的全过程:iOS触摸事件的流动(想有更清晰的了解的看)
直接引用里面的一张图:

image.png

参考资料:
响应者链及相关机制总结

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

推荐阅读更多精彩内容