1. 程式人生 > >實現點選穿透、執行下層檢視的點選事件

實現點選穿透、執行下層檢視的點選事件

原文 , 只記錄我看懂的部分

http://www.cnblogs.com/klaus/archive/2013/04/22/3036692.html

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。

  1. 若返回NO,則hitTest:withEvent:返回nil;
  2. 若返回YES,則向當前檢視的所有子檢視(subviews)傳送hitTest:withEvent:訊息,所有子檢視的遍歷順序是從top到bottom,即從subviews陣列的末尾向前遍歷,直到有子檢視返回非空物件或者全部子檢視遍歷完畢;
  3. 若第一次有子檢視返回非空物件,則hitTest:withEvent:方法返回此物件,處理結束;
  4. 如所有子檢視都返回非,則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的生命週期不再改變,即使觸控操作後續移動到其他檢視之上。

這裡有幾個例子:

  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按鈕可以響應點選事件。