iOS開發之再議事件的產生和傳遞

事件的產生和傳遞(事件如何從父控件傳遞到子控件並尋找到最合適的view、尋找最合適的view的底層實現、攔截事件的處理)-> 找到最合適的view後事件的處理(touches方法的重寫,也就是事件的響應)

iOS中的事件可以分爲3大類型:
1.觸摸事件
2.加速計事件
3.遠程控制事件

每當我們點擊了一下iOS設備的屏幕,UIKit就會生成一個事件對象UIEvent,然後會把這個Event分發給當前activeapp(官方原文說:Then it places the event object in the active app’s event queue.)

告知當前活動的app有事件之後,UIApplication單例就會從事件隊列中去取最新的事件,然後分發給能夠處理該事件的對象。UIApplication獲取到Event之後,Application就糾結於到底要把這個事件傳遞給誰,這時候就要依靠HitTest來決定了。

iOS中,hit-Testing的作用就是找出這個觸摸點下面的View是什麼,HitTest會檢測這個點擊的點是不是發生在這個View上,如果是的話,就會去遍歷這個Viewsubviews,直到找到最小的能夠處理事件的view,如果整了一圈沒找到能夠處理的view,則返回自身。來一個簡單的圖說明一下。


假設我們現在點擊到了圖中的Ehit-testing將進行如下步驟的檢測(不包含重寫hit-test並且返回非默認View的情況)
1、觸摸點在ViewA內,所以檢查ViewASubview BC
2、觸摸點不在ViewB內,觸摸點在ViewC內部,所以檢查ViewCSubview DE
3、觸摸點不在ViewD內,觸摸點發生在ViewE內部,並且ViewE沒有subview,所以ViewE屬於ViewA中包含這個點的最小單位,所以ViewE變成了該次觸摸事件的hit-TestView

PS.
1、默認的hit-testing順序是按照UIViewSubviews的逆順序
2、如果View的同級別Subview中有重疊的部分,則優先檢查頂部的Subview,如果頂部的Subview返回nil再檢查底部的Subview
3Hit-Test也是比較聰明的,檢測過程中有這麼一點,就是說如果點擊沒有發生在某View中,那麼該事件就不可能發生在ViewSubview中,所以檢測過程中發現該事件不在ViewB內,也直接就不會檢測在不在ViewF內。也就是說,如果你的Subview設置了clipsToBounds=NO,實際顯示區域可能超出了superViewframe,你點擊超出的部分,是不會處理你的事件的,就是這麼任性!

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;
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章