iOS -响应者链

引用:

https://blog.csdn.net/weixin_72437555/article/details/138610888
https://zhuanlan.zhihu.com/p/476477484

截屏2024-09-16 16.37.55.png
截屏2024-09-16 16.39.18.png
截屏2024-09-16 16.39.33.png
截屏2024-09-16 16.42.49.png
image.png
截屏2024-09-16 16.56.48.png
截屏2024-09-16 16.56.10.png
截屏2024-09-16 16.56.17.png

总结

. 当触摸事件发生后,系统会自动生成一个UIEvent对象,记录事件产生的时间和类型
. 然后系统会将UIEvent事件加入到一个由UIApplication管理的事件队列中
. 然后UIApplication将事件分发给UIWindow,主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件
. 不断递归调用hitTest方法来找到第一响应者
. 如果第一响应者无法响应事件,那么按照响应者链往上传递,也就是传递给自己的父视图
. 一直传递直到UIApplication,如果都无法响应则事件被丢弃

触摸事件由触屏生成后如何传递到当前应用?

系统响应阶段

1.指触碰屏幕,屏幕感应到触碰后,将事件交由IOKit处理。

2.IOKit将触摸事件封装成一个IOHIDEvent对象,并通过mach port传递给SpringBoad进程。

mach port 进程端口,各进程之间通过它进行通信。
SpringBoad.app 是一个系统进程,可以理解为桌面系统,可以统一管理和分发系统接收到的触摸事件。
3.SpringBoard进程因接收到触摸事件,将触摸事件交给前台app进程来处理。

APP响应阶段

1.APP进程的mach port接受到SpringBoard进程传递来的触摸事件,主线程的runloop被唤醒,触发了source1回调。

2.source1回调又触发了一个source0回调,将接收到的IOHIDEvent对象封装成UIEvent对象,此时APP将正式开始对于触摸事件的响应。

3.source0回调内部将触摸事件添加到UIApplication对象的事件队列中。事件出队后,UIApplication开始一个寻找最佳响应者的过程,这个过程又称hit-testing,另外,此处开始便是与我们平时开发相关的工作了。

4.寻找到最佳响应者后,接下来的事情便是事件在响应链中的传递及响应了。

5.触摸事件历经坎坷后要么被某个响应对象捕获后释放,要么致死也没能找到能够响应的对象,最终释放。

至此,这个触摸事件的使命就算终结了。runloop若没有其他事件需要处理,也将重归于眠,等待新的事件到来后唤醒。

参考博客事件传递与响应 详解(精通iOS系列)
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_72437555/article/details/138610888

以下内容重复:

所以事件的传递顺序是这样的:
  产生触摸事件->UIApplication事件队列->[UIWindow hitTest:withEvent:]->返回更合适的view->[子控件 hitTest:withEvent:]->返回最合适的view

事件传递给窗口或控件的后,就调用hitTest:withEvent:方法寻找更合适的view。所以是,先传递事件,再根据事件在自己身上找更合适的view。
不管子控件是不是最合适的view,系统默认都要先把事件传递给子控件,经过子控件调用子控件自己的hitTest:withEvent:方法验证后才知道有没有更合适的view。即便父控件是最合适的view了,子控件的hitTest:withEvent:方法还是会调用,不然怎么知道有没有更合适的!即,如果确定最终父控件是最合适的view,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的。

当UIApplication发送事件到keyWindow时,keyWindow会调用-hitTest:withEvent:方法来寻找最适合处理事件的视图。假设事件已经传递到某视图view,选择出能响应视图的逻辑如下:

1、首先会判断该视图自身能否处理该触摸事件,如果不能响应,则不通过pointInside方法,则hitTest方法直接返回nil;
2、如果该View可以响应,则调用-pointInside:withEvent:判断是否在显示区域上,如果不在其区域中,则返回NO,同时-hitTest:withEvent:也返回nil;
3、如果步骤2中返回YES,表示在当前View的范围中,接着先倒序遍历该视图的子视图;
4、如果步骤3中没有子视图,或者没有任何一个子视图能够响应该触摸事件,则返回该视图自身,表示只有自身可以处理该事件。

作者:夜凉听风雨
链接:https://www.jianshu.com/p/777487a6d87c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
** 技巧:**

想让谁成为最合适的view就重写谁自己的父控件的hitTest:withEvent:方法返回指定的子控件,或者重写自己的hitTest:withEvent:方法 return self。但是,建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view!
原因在于在自己的hitTest:withEvent:方法中返回自己有时候会出现问题。因为会存在这么一种情况:当遍历子控件时,如果触摸点不在子控件A自己身上而是在子控件B身上,还要要求返回子控件A作为最合适的view,采用返回自己的方法可能会导致还没有来得及遍历A自己,就有可能已经遍历了点真正所在的view,也就是B。这就导致了返回的不是自己而是触摸点真正所在的view。所以还是建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view!

1>用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件
2>找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理touchesBegan…touchesMoved…touchedEnded…
3>这些touches方法的默认做法是将事件顺着响应者链条向上传递(也就是touch方法默认不处理事件,只传递事件),将事件交给上一个响应者进行处理

事件的链有两条:事件的响应链;Hit-Testing 时事件的传递链。

响应链:由离 户最近的view向系统传递。 initial view –> super view –> .....–> view controller –> window –> Application –> AppDelegate

Hit-Testing 链:由系统向离 户最近的view传递。 UIKit –> active app's event queue –> window –> root view –>......–>lowest view

事件的传递与响应:

1、当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的传递,也就是寻找最合适的view的过程。

2、接下来是事件的响应。首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView);如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传 递;(对于第二个图视图控制器本身还在另一个视图控制器中,则继续交给父视图控制器的根视图,如果根视图不能处理则交给父视图控制器处理);一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃

3、在事件的响应中,如果某个控件实现了touches...方法,则这个事件将由该控件来接受,如果调用了[supertouches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….方法

作者:VV木公子
链接:https://www.jianshu.com/p/2e074db792ba
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


iOS中的事件

触摸事件,加速事件(摇一摇),远程控制事件(耳机线控,窗口播放)

以最常见的触摸事件为例,当触摸手机屏幕时操作系统会将这个事件添加到由UIApplication管理的事件队列中(FIFO)UIApplication发送事件到应用程序的主窗口(Window)Window会在图层结构中找到最合适的图层来处理事件。

UIResponder

UIResponder类是专门用来响应用户的操作处理各种事件的,iOS中大部分控件都继承自UIResponder,默认响应事件的方法如下(触摸事件)

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; //触摸开始,手指接触屏幕
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; //拖动
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//触摸结束,手机离开屏幕
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//中断,被手势或者系统中断
事件传递链

UIApplication传递事件到当前Window是明确的,接下来就是从Window开始找最佳响应视图,此过程有两个重要的方法:

hitTest方法继承自UIView(UIWindow是继承自UIView的)。从UIApplication开始调用Window的hitTest方法,默认是递归调用的。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    return [super pointInside:point withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    return [super hitTest:point withEvent:event];
}

传递过程如下:

1.系统从UIApplication开始,当前window调用hitTest,hitTest内部会通过以下条件判断window能否能响应事件

不允许交互:userInteractionEnabled=NO
隐藏:hidden = YES
透明度:alpha < 0.01,alpha小于0.01为全透明
2.如果能响应,该函数内部会调用pointInside判断当前触摸点是不是在视图范围内

3.如果在window范围内,开始反向遍历window的子视图列表subviews,遍历的同时会调用subviews中每个子视图的hitTest,判断逻辑和上面的一样,如果找到循环就会停止。

4.此过程会递归,直到找到最外层合适的view,最后返回的view就是最佳响应视图。

一种hitTest可能的实现方式如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    if (!self.userInteractionEnabled|| self.hidden || self.alpha == 0.0){
        return nil;
    }
    if (![self pointInside:point withEvent:event]){
        return nil;
    }
    // 后加入的视图在图层上方,所以反向遍历是合理的
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--)
    {
        UIView *view = self.subviews[i];
        // 坐标的转换
        CGPoint subPoint = [self convertPoint:point toView:view];
        // 继续递归
        UIView *lastView = [view hitTest:subPoint withEvent:event];
        if (lastView)
        {
            return lastView;
        }
    }
    return self;
}

以上,这就是事件传递过程,由内往外的传递过程(从window开始到最外层视图 )

此过程查找结束返回最终的view,UIApplication会调用UIWindow的sendEvent,从而触发对应的响应方法:

PS:这里通过在UIWIndow中重写sendEvent而不调用super的实现,你会发现所有的点击事件都不会触发

  • (void)sendEvent:(UIEvent *)event;
    以下是需要注意的点:

实际调用hitTest过程,系统为了找到精准的触摸点会多次调用

如果重写hitTest返回self,传递过程就会终止,当前view就是最合适的view;返回nil,传递也会终止,父视图superView就是最合适的view

如果遍历subviews的过程都没找到合适的view,那么subviews中的子view的hitTest会都会被被调用一次

hitTest会调用pointInside判断当前视图是否在点击区域,所以超出父视图边界的控件无法响应事件

同一个view上的两个子视图有重叠部分,后加入的视图会被加入到事件传递链

事件响应链

首先,响应者链中的各个响应者都继承自UIResponder,常见的UIView,viewController,UIWindow以及AppDelegate都继承自UIResponder。响应者链上的响应者在hitTest过程中就已经确定,可以通过迭代nextResponder查看所有的响应者。

事件响应链如下:

通过hitTest返回的view为当前事件的第一响应者,nextResponder为上一个响应者

如果当前view默认不去重写,或者重写调用了父类的实现,响应就会就会沿着响应者链向上传递(上一个响应者一般是superView,可以通过nextResponder属性获取上一个响应者)

如果上一个响应者是viewController,由viewController的view处理,view本身没处理,则传递给viewController本身

重复上述过程,直到传递到window,window如果也不能处理,传递到UIApplication,如果UIApplication的delegate继承自UIResponder,则交给delegate处理,delegate也不处理最后丢弃

以上就是响应者链,事件响应过程是从外向内传递,和事件传递的过程正好相反

通过遍历查找所有响应者:

UIResponder *respon = self;
while (respon) {
    NSLog(@"%@",respon);
    respon = respon.nextResponder;
}

作者:你duck不必呀
链接:https://www.jianshu.com/p/70e8bc099966
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

推荐阅读更多精彩内容

  • 一篇搞定事件传递、响应者链条、hitTest和pointInside的使用发生触摸事件后,系统会将该事件加入到一个...
    克鲁德李阅读 1,102评论 0 1
  • 一、响应者链(Responder Chain) 先来说说响应者对象(Responder Object),顾名思义,...
    像小强一样活着阅读 6,867评论 8 76
  • 先来明确几个概念 响应者对象可以进行事件处理的对象。用户进行了某个操作,系统会将该操作包装成一个Event事件对象...
    yaqiong阅读 183评论 0 0
  • 一、概述 iOS 响应者链(Responder Chain)是支撑 App 界面交互的重要基础,点击、滑动、旋转、...
    小道萧兮阅读 3,052评论 2 12
  • iOS中的响应者链是指UIKit 生成的UIResponder对象组成的链表,它是iOS里一切事件相关(触摸事件、...
    左左4143阅读 513评论 0 0