引用:
https://blog.csdn.net/weixin_72437555/article/details/138610888
https://zhuanlan.zhihu.com/p/476477484
总结
. 当触摸事件发生后,系统会自动生成一个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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。