iOS面试题:事件响应链是如何形成的?
当我们触碰到屏幕的时候,整个iOS系统发生了什么呢?
这里有个思路需要转变一下,本质上,我们开发的app
,里面所有的视图都是虚拟的,只是一堆代码,看起来,你的app
有许多View
的堆叠,而且是有层次的,你看起来触碰到了最上面的一个View
,事实上屏幕只有一块啊,你触碰到的是冷冰冰的屏幕,因此第一个感知触摸事件的是操作系统,是他最先检测到屏幕上的压力,而不是你看上去触摸到的那个视图哟。
注意,这并不意味着系统会立刻处理此事件
虽然看起来app
里面的控件和View
是有层次的,但事实上屏幕只有一块,而且没有层次
我们如何才能构架出有层次的View
呢?
苹果的办法是:画布模式,大家都见过画画,你如果在同一个地方画2笔,第二笔会覆盖第一笔,同样的,在屏幕上的同一地方有两个View
,第二View
会覆盖第一个View
,因此在视觉上有了层次
也因此,当你触摸到屏幕上某个点的时候,其实系统并不能立刻确定你触摸的哪块View
,因为多块View
会有叠加、覆盖
系统会如何做呢?正如上文提到的,第一个响应的是底层系统
如上图,当我们点击View E
的位置时,系统先响应,然后它会调用方法:
该方法接受位置参数CGPoint
,并从底层开始按照subview
的顺序,测试该CGPoint
在哪个View
上,如果在该View
上,则继续测试是否在View的subview
上,对照上图,顺序如下:
1.触摸的CGPoint
在View A
上吗?在的,继续测试A
的子视图View B
、View C
2.在View B
上吗?不。在View B
上吗?在,继续B
的子视图View D
、View E
3.在View D
上吗?不,且D
无subview
,结束此分支
4.在View E
上吗?在,E
无subview
,结束此分支
5.结果形成了一个链,View A
-->View C
-->View E
还记得吗,系统第一时间检测到了触摸,但是并不立刻处理,而是通过上述的测试,得到了一个响应链,该链的最后一个,就是逻辑上最上层的视图,也就是这次触摸的First Responder
需要注意的是,所有的响应链都是父子视图的关系哟,如果View A
、View C
、 VIew E
只是视觉上遮盖了,但是却不是superview
、subview
的关系,则事件是不会在两者之间传递的
从底层往上的好处就是,一层层测试的过程中,响应链已经形成,当View E
无法处理此事件怎么办?按照响应链往上回溯即可,一直回溯到application
,也无人处理此事件,则将事件丢弃,如下图
如果First Responder
不响应,则往上传给下一个View
,直到传给UIApplication
,依然无人响应则丢弃事件
如何处理事件?
我们获取了触摸事件,那么如何响应这个触摸呢?
其中一个是,手势识别(Gesture Recognizer
): iOS
提供了UITapGestureRecognizer
来识别手势,它必须挂在某个View
上,当某个View
上发生了触摸事件,UITapGestureRecognizer
就会识别该触摸,如果符合我们定义的手势,则触发一个事件
UITapGestureRecognizer *tapG =[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick:)];
tapG.delegate=self;
tapG.numberOfTapsRequired=2; //需要触碰2次
tapG.numberOfTouchesRequired=1;//需要一个手指
[blueView addGestureRecognizer:tapG];//把手势识别关联到blueView上
上面就是一个手势识别,当你在blueView
上,单指触碰2次,就会触发tapClick:
方法
以上是比较固定,好识别的手势,有些手势是没有规律可循的,比如切水果游戏中,是一指滑动切水果,角色扮演游戏中,滑动手指会使人物走动,画图板app
,手指移动会留下笔画,如何定义这些手势?
事实上,我们需要在手指移动的同时就响应它,而不是等他结束了再响应,否则你的游戏就会有延时,你的画板就会等你手指离开屏幕才出现笔画,多么low的app
苹果提供了方法,让你可以实时的响应手势
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
从命名就可以看出,以上方法是何作用,顺带提一句这也是我们推荐的代码风格,从命名即可猜出用途
以上方法是UIResponder
的方法,只要是他的子类,都是可以响应触摸事件的,并且按响应链顺序调用,子视图不响应,就调父视图的
哪些是UIResponder
的子类呢,大家可以打开Xcode
,按下shift+commond+0
,打开帮助文档,可以看到每个类的继承顺序,截取几个给大家看看
看到了吗,UIView
、UIViewController
都是UIResponder
的方法,现在想想,为啥一个View
覆盖了下面的UIScrollView
,你在View
上滑动手指,下面的scrollView
就不会滑动吗?如果这个View
是scrollView
的子视图,你按住View
滑动,整个scrollView
都滑动了呢?
答案:覆盖的情况下,他们不是父子视图的关系,不在一条响应链上,事件并没有传给scrollView
,当然不会出现滑动。如果是子视图,则在一个响应链上,View
本身如果不加手势识别,并不会处理触摸事件,就会传给scrollView
,而scrollView
已经写好如何响应滑动,就出现滑动的效果
更多:iOS面试题
更多:《BAT面试答案文集.PDF》,获取可加iOS技术交流圈:937194184。
收录:原文地址