从事件发生到其处理的对象,事件的传递要经过一个相当长且特殊的过程。当用户点击设备屏幕的时,iOS捕捉到一系列的触摸,将其打包到UIEvent对象中加入到当前的application的事件队列中。单例UIApplication对象管理当前应用程序,从事件队列中取出事件并将其分发处理,传递给应用程序的key window(当前接收用户事件的窗口),window对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,称之为hit-test view
。
hitTest:withEvent:方法的处理流程如下:
首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
若返回NO,则hitTest:withEvent:返回nil;
若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从top到bottom(z序从大到小),即从subviews数组的末尾向前遍历(从最上层视图开始),直到有子视图返回非空对象或者全部子视图遍历完毕;
若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
如所有子视图都返回空,则hitTest:withEvent:方法返回自身(self)。
hitTest:withEvent:方法忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=No)的视图(UIImageView默认值userInteractionEnabled=No
),以及alpha级别小于等于0.01(alpha<=0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。可参考UIView Class Reference
hitTest:withEvent:
This method traverses the view hierarchy by sending the pointInside:withEvent: message to each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit. Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content.
Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clipsToBounds property is set to NO and the affected subview extends beyond the view’s bounds.
对于每个触摸操作都会有一个UITouch对象,UITouch对象用来表示一个触摸操作,即一个手指在屏幕上按下、移动、离开的整个过程。UITouch对象在触摸操作的过程中在不断变化,所以在使用UITouch对象时,不能直接retain,而需要使用其他手段存储UITouch的内部信息。UITouch对象有一个view属性,表示此触摸操作初始发生所在的视图,即上面检测到的hit-test view,此属性在UITouch的生命周期不再改变,即使触摸操作后续移动到其他视图之上。
示例:在视图A上先后add上子视图B,C,D,如图所示
点击视图D内部,视图A调用hitTest:withEvent:
,然后A调用pointInside:withEvent:
方法,返回Yes,同时向A的左右子视图发送hitTest:withEvent:
消息,按视图层级从顶端到底部开始遍历调用,即按D->C->B顺序进行,由于D视图调用
pointInside:withEvent:
方法,返回Yes,也就是说D调用hitTest:withEvent:
时返回非空对象(视图D),事件处理结束,视图D即为hit-test view。
在编写例子过程中遇到一个问题,每次点击的时候,都会调用当前view的hitTest:withEvent:
方法三次(与当前view上子视图个数无关),每次输出的point的值基本上是相同的,在stackoverflow上面搜索了一下,也没找到满意的答案,这里有个回帖可以参考下
hitTest:
is a utility method designed to find a view at a specific point. ItDOES NOT represent a user tapping on the touch screen. It is totally sensible for hitTest to be called several times in response to the same event; all the method is supposed to do is return the view under the point and itSHOULD NOT trigger any side effects.
If you want to track touch events, you should override touchesBegan:
and friends.
(截自stackoverflow)