在IOS系统中,用户会触发若干事件,系统会分发这些事件,并交由恰当的对象来处理。IOS事件分发流程大致如下:
1、用户触发事件,系统将事件封装为event object对象,放入当前APP的event queue中。(touch事件对应的event object包含了一系列的touch对象,而motion对应的event则因事件不同而不同)
2、由当前APP对应的单例对象UIApplication在queue头中取出event object,分发至当前APP的主window object
3、主window object会将event object分发至当前最合适的initial object来处理。(对于touch event,initial object为hit-test view, 对于motion,remote control,initial object为first responder)
4、当然initial object也可以不处理该event,这样,event object会沿着一条规定的路径一路向上传递直到有object可以处理该event,或者抛弃该event。而这个向上寻找对应的处理event的object的路径,叫做Responder Chian。它始于initial object,终于application object。该chain上的所有对象,均继承与UIResponder,通过nextResponder方法,可以找到当前对象响应链上的下一个对象。
hit-testing 与 hit-test view
如前所述,当主window对象分发event object时,若event object为touch event,则最终hit-test view获得最先响应touch事件权利,而判断hit-test view的过程,叫做hit-testing。
hit-testing是一个递归的过程,执行过程如下
1、判断touch point是否在当前view中
2、若在,这继续判断touch point是否在当前view的子view中,若不在,则直接返回,不在继续判断。
3、这样一直递归判断,直到touch point所在的最底层的subview,并返回该subview作为响应touch event的hit-test view。
4、确定了hit-test view,系统将会使touch event交由该view处理,若该view没有处理该事件,则touch event会沿着Responder Chain一路向上寻找可以响应touch event的Responder。
在hit-testing中,系统会调用每个view的hitTest:withEvent:
方法,该方法最终会返回一个view作为hit-test view。而在hitTest:withEvent:
方法中,view又会调用 pointInside:withEvent:来测试当前view是否在touch的范围内,若是,则
pointInside:withEvent:返回YES,系统继续依次调用subview们的
hitTest:withEvent:
方法,若pointInside:withEvent:返回NO,则hitTest:withEvent:
直接返回nil。
注意到hitTest:withEvent:
上面的这种实现,那么有一种情况是当subview的范围超出父view时,若touch是发生在超出的那块区域,则subview默认不会得到响应touch event的机会,因为在其父view判断 pointInside:withEvent:就已经返回了NO,
hitTest:withEvent:
不会继续向下判断。
Apple网站上的这么一段说明,很好的解释了hit-testing的过程:
To illustrate, suppose that the user touches view E in Figure 2-1. iOS finds the hit-test view by checking the subviews in this order:
-
The touch is within the bounds of view A, so it checks subviews B and C.
-
The touch is not within the bounds of view B, but it’s within the bounds of view C, so it checks subviews D and E.
-
The touch is not within the bounds of view D, but it’s within the bounds of view E.
View E is the lowest view in the view hierarchy that contains the touch, so it becomes the hit-test view.
了解了上面hit-testing的判断过程,下面就有一个有趣的问题:
如图,绿色的view是红色view的subview,那么当我在蓝点所示的位置点击屏幕时,绿色view是否能够获取到该事件呢?
答案是否定的,因为系统在进行hit-testing时,会首先判断事件发生的point是否在其父view,即红色view范围内。很明显,point并不在红色view中,因此系统就放弃了继续判断其绿色子view是否在事件范围内。若想使得sub view一定要有所相应,那么应该设置父view的
clipsToBounds属性为YES,强制子view在父view的范围内显示。
参考资料