事件的產生和傳遞(事件如何從父控件傳遞到子控件並尋找到最合適的view、尋找最合適的view的底層實現、攔截事件的處理)-> 找到最合適的view後事件的處理(touches方法的重寫,也就是事件的響應)
iOS中的事件可以分爲3大類型:
1.觸摸事件
2.加速計事件
1.觸摸事件
2.加速計事件
3.遠程控制事件
每當我們點擊了一下iOS設備的屏幕,UIKit就會生成一個事件對象UIEvent,然後會把這個Event分發給當前active的app(官方原文說:Then
it places the event object
in the active app’s event queue.)
告知當前活動的app有事件之後,UIApplication單例就會從事件隊列中去取最新的事件,然後分發給能夠處理該事件的對象。UIApplication獲取到Event之後,Application就糾結於到底要把這個事件傳遞給誰,這時候就要依靠HitTest來決定了。
告知當前活動的app有事件之後,UIApplication單例就會從事件隊列中去取最新的事件,然後分發給能夠處理該事件的對象。UIApplication獲取到Event之後,Application就糾結於到底要把這個事件傳遞給誰,這時候就要依靠HitTest來決定了。
iOS中,hit-Testing的作用就是找出這個觸摸點下面的View是什麼,HitTest會檢測這個點擊的點是不是發生在這個View上,如果是的話,就會去遍歷這個View的subviews,直到找到最小的能夠處理事件的view,如果整了一圈沒找到能夠處理的view,則返回自身。來一個簡單的圖說明一下。
假設我們現在點擊到了圖中的E,hit-testing將進行如下步驟的檢測(不包含重寫hit-test並且返回非默認View的情況)
1、觸摸點在ViewA內,所以檢查ViewA的Subview B、C
2、觸摸點不在ViewB內,觸摸點在ViewC內部,所以檢查ViewC的Subview D、E
3、觸摸點不在ViewD內,觸摸點發生在ViewE內部,並且ViewE沒有subview,所以ViewE屬於ViewA中包含這個點的最小單位,所以ViewE變成了該次觸摸事件的hit-TestView
PS.
1、默認的hit-testing順序是按照UIView中Subviews的逆順序
2、如果View的同級別Subview中有重疊的部分,則優先檢查頂部的Subview,如果頂部的Subview返回nil,再檢查底部的Subview
3、Hit-Test也是比較聰明的,檢測過程中有這麼一點,就是說如果點擊沒有發生在某View中,那麼該事件就不可能發生在View的Subview中,所以檢測過程中發現該事件不在ViewB內,也直接就不會檢測在不在ViewF內。也就是說,如果你的Subview設置了clipsToBounds=NO,實際顯示區域可能超出了superView的frame,你點擊超出的部分,是不會處理你的事件的,就是這麼任性!
Hit-Test的檢查機制如上所示,當確定了Hit-TestView時,如果當前的application沒有忽略觸摸事件(UIApplication:isIgnoringInteractionEvents),則application就會去分發事件(sendEvent:->keywindow:sendEvent:)
UIView中提供兩個方法用來確定hit-testing View,如下所示
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
當一個View收到hitTest消息時,會調用自己的pointInside:withEvent:方法,如果pointInside返回YES,則表明觸摸事件發生在我自己內部,則會遍歷自己的所有Subview去尋找最小單位(沒有任何子view)的UIView,如果當前View.userInteractionEnabled
= NO,enabled=NO(UIControl),或者alpha<=0.01,
hidden等情況的時候,hitTest就不會調用自己的pointInside了,直接返回nil,然後系統就回去遍歷兄弟節點。簡而言之,可以寫成這樣- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden)
{
return nil;
}
BOOL inside = [self pointInside:point withEvent:event];
UIView *hitView = nil;
if (inside) {
NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
for (UIView *subview in enumerator) {
hitView = [subview hitTest:point withEvent:event];
if (hitView) {
break;
}
}
if (!hitView)
{
hitView = self;
}
return hitView;
} else {
return nil;
}
}