天天看點

hitTest:withEvent: [轉]hitTest:withEvent:方法流程

[轉]hitTest:withEvent:方法流程

此方法可實作點選穿透、點選下層視圖功能。

一. hitTest:withEvent:調用過程

iOS系統檢測到手指觸摸(Touch)操作時會将其放入目前活動Application的事件隊列,UIApplication會從事件隊列中取出觸摸事件并傳遞給key window(目前接收使用者事件的視窗)處理,window對象首先會使用hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View),即需要将觸摸事件傳遞給其處理的視圖,稱之為hit-test view。

window對象會在首先在view hierarchy的頂級view上調用hitTest:withEvent:,此方法會在視圖層級結構中的每個視圖上調用pointInside:withEvent:,如果pointInside:withEvent:傳回YES,則繼續逐級調用,直到找到touch操作發生的位置,這個視圖也就是hit-test view。

hitTest:withEvent:方法的處理流程如下:

  1. 首先調用目前視圖的pointInside:withEvent:方法判斷觸摸點是否在目前視圖内;
  2. 若傳回NO,則hitTest:withEvent:傳回nil;
  3. 若傳回YES,則向目前視圖的所有子視圖(subviews)發送hitTest:withEvent:消息,所有子視圖的周遊順序是從top到bottom,即從subviews數組的末尾向前周遊,直到有子視圖傳回非空對象或者全部子視圖周遊完畢;
  4. 若第一次有子視圖傳回非空對象,則hitTest:withEvent:方法傳回此對象,處理結束;
  5. 如所有子視圖都傳回非,則hitTest:withEvent:方法傳回自身(self)。

hitTest:withEvent:方法忽略隐藏(hidden=YES)的視圖,禁止使用者操作(userInteractionEnabled=YES)的視圖,以及alpha級别小于0.01(alpha<0.01)的視圖。如果一個子視圖的區域超過父視圖的bound區域(父視圖的clipsToBounds 屬性為NO,這樣超過父視圖bound區域的子視圖内容也會顯示),那麼正常情況下對子視圖在父視圖之外區域的觸摸操作不會被識别,因為父視圖的pointInside:withEvent:方法會傳回NO,這樣就不會繼續向下周遊子視圖了。當然,也可以重寫pointInside:withEvent:方法來處理這種情況。

對于每個觸摸操作都會有一個UITouch對象,UITouch對象用來表示一個觸摸操作,即一個手指在螢幕上按下、移動、離開的整個過程。UITouch對象在觸摸操作的過程中在不斷變化,是以在使用UITouch對象時,不能直接retain,而需要使用其他手段存儲UITouch的内部資訊。UITouch對象有一個view屬性,表示此觸摸操作初始發生所在的視圖,即上面檢測到的hit-test view,此屬性在UITouch的生命周期不再改變,即使觸摸操作後續移動到其他視圖之上。

二.定制hitTest:withEvent:方法

如果父視圖需要對對哪個子視圖可以響應觸摸事件做特殊控制,則可以重寫hitTest:withEvent:或pointInside:withEvent:方法。

這裡有幾個例子:

  1. hitTest Hacking the responder chain

    在此例子中button,scrollview同為topView的子視圖,但scrollview覆寫在button之上,這樣在在button上的觸摸操作傳回的hit-test view為scrollview,button無法響應,可以修改topView的hitTest:withEvent:方法如下:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *result = [super hitTest:point withEvent:event];
        CGPoint buttonPoint = [underButton convertPoint:point fromView:self];
        if ([underButton pointInside:buttonPoint withEvent:event]) {
            return underButton;
        }
        return result;
    }
               
    這樣如果觸摸點在button的範圍内,傳回hittestView為button,從button按鈕可以響應點選事件。
  2. Paging-enabled UIScrollView with Previews

    BSPreviewScrollView 

    關于這兩個例子,可以看之前文章的說明,見Paging-enabled UIScrollView