iOS hitTest button在視圖外也能響應事件

文章目錄

? 一、什么是hitTest

? 二、hitTest的調用順序

? 三、事件的傳遞順序

? 四、hitTest的實現(xiàn)思路

? 五、hitTest的運用場景

? 1、事件穿透

? 2、子視圖超出父視圖范圍

一、什么是hitTest

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

point? ? ? : 在接收器的局部坐標系(界)中指定的點。

event? ? ? : 系統(tǒng)保證調用此方法的事件。如果從事件處理代碼外部調用此方法,則可以指定nil。

returnValue : 視圖對象是當前視圖和包含點的最遠的后代。如果點完全位于接收方的視圖層次結構之外,則返回nil。

hitTest: withEvent: 是UIView 里面的一個方法,該方法的作用 在于 : 在視圖的層次結構中尋找一個最適合的 view 來響應觸摸事件。

該方法會被系統(tǒng)調用,調用的時候,如果返回為nil,即事件有可能被丟棄,否則返回最合適的view 來響應事件。(怎么感覺這句話這么別扭,你們覺得呢?)

二、hitTest 的調用順序

touch -> UIApplication -> UIWindow -> UIViewController.view -> subViews -> ....-> 合適的view

? ? 1

三、事件的傳遞順序

事件傳遞順序與hitTest 的調用順序 恰好相反

view -> superView ...- > UIViewController.view -> UIViewController -> UIWindow -> UIApplication -> 事件丟棄

? ? 1

文字說明:

1、 首先由 view 來嘗試處理事件,如果他處理不了,事件將被傳遞到他的父視圖superview

2、superview 也嘗試來處理事件,如果他處理不了,繼續(xù)傳遞他的父視圖

UIViewcontroller.view

3、UIViewController.view嘗試來處理該事件,如果處理不了,將把該事件傳遞給UIViewController

4、UIViewController嘗試處理該事件,如果處理不了,將把該事件傳遞給主窗口Window

5、主窗口Window嘗試來處理該事件,如果處理不了,將傳遞給應用單例Application

6、如果Application也處理不了,則該事件將會被丟棄

蘋果官方提供的示意圖如下 :


四、hitTest的實現(xiàn)思路

常見的視圖不響應事件不外乎如下幾種情況

1、view.userInteractionEnabled = NO;

2、view.hidden = YES;

3、view.alpha < 0.05

4、view 超出 superview 的 bounds

那么hitTest 就可根據(jù)上面 結果 大概模擬下 hitTest 方法的大概實現(xiàn)

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

{

? ? // 如果交互未打開,或者透明度小于0.05 或者 視圖被隱藏

? ? if (self.userInteractionEnabled == NO || self.alpha < 0.05 || self.hidden == YES)

? ? {

? ? ? ? return nil;

? ? }

? ? // 如果 touch 的point 在 self 的bounds 內

? ? if ([self pointInside:point withEvent:event])

? ? {

? ? ? ? for (UIView *subView in self.subviews)

? ? ? ? {

? ? ? ? ? ? //進行坐標轉化

? ? ? ? ? ? CGPoint coverPoint = [subView convertPoint:point fromView:self];

? ? ? ? ? // 調用子視圖的 hitTest 重復上面的步驟。找到了,返回hitTest view ,沒找到返回有自身處理

? ? ? ? ? ? UIView *hitTestView = [subView hitTest:coverPoint withEvent:event];

? ? ? ? ? ? if (hitTestView)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return hitTestView;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return self;

? ? }

? ? return nil;

}

步驟文字說明

1、首先在當前視圖的hitTest方法中調用pointInside方法判斷觸摸點是否在當前視圖內

2、若pointInside方法返回NO,說明觸摸點不在當前視圖內,則當前視圖的hitTest返回nil,該視圖不處理該事件

3、若pointInside方法返回YES,說明觸摸點在當前視圖內,則從最上層的子視圖開始(即從subviews數(shù)組的末尾向前遍歷),遍歷當前視圖的所有子視圖,調用子視圖的hitTest方法重復步驟1-3

4、直到有子視圖的hitTest方法返回非空對象或者全部子視圖遍歷完畢

5、若第一次有子視圖的hitTest方法返回非空對象,則當前視圖的hitTest方法就返回此對象,處理結束

6、若所有子視圖的hitTest方法都返回nil,則當前視圖的hitTest方法返回當前視圖本身,最終由該對象處理觸摸事件

結合下面的例子理解上面的步驟 :


其中 : 紅點為touch 點

當點擊ViewE時,hitTest執(zhí)行順序如下:

先看看點擊大致走向圖如下,其中,?部分為執(zhí)行pointInside為YES部分,X部分執(zhí)行pointInside為NO部分,最終hitTest返回ViewE。

1、首先調用ViewA的hitTest方法,由于觸摸點在其范圍內,pointInside返回YES,遍歷其子視圖,依次調用ViewB和ViewC的hitTest方法

2、執(zhí)行ViewB的hitTest方法,由于觸摸點是不在ViewB內,其pointInside方法返回NO,hitTest返回nil

3、執(zhí)行ViewC的hitTest方法,由于觸摸點是在ViewC內,其pointInside方法返回YES,遍歷其子視圖,依次調用ViewD和ViewE的hitTest方法

4、執(zhí)行ViewD的hitTest方法,由于觸摸點是不在ViewD內,其pointInside方法返回NO,所以其hitTest返回nil

5、執(zhí)行ViewE的hitTest方法,由于觸摸點是在 ViewE內,其pointInside方法返回YES,由于其沒有子視圖了,其hitTest返回其本身

6、最終,由ViewE來響應該點擊事件

五、hitTest的運用場景

1、事件穿透

青色的遮罩層(maskView) 在黃色 按鈕(btn) 的上層,即 視圖的添加順序為,先添加 黃色 按鈕(btn),再添加 青色的遮罩(maskView)。 有這么個需求,點擊按鈕修改maskView 的背景顏色。

注意 : btn 和 maskview 有重疊部分。視圖都添加到UIViewController.view 。

1>、首先,調用UIViewController.view的hitTest。

2>、其次,遍歷子視圖,進行坐標轉化,判斷 point 是否在 bounds 內,發(fā)現(xiàn) maskview 和 btn 都滿足

3>、再者,調用maskview 和 btn 的hitTest 方法,到這里,我們的目標的hitText View 很顯然是btn,那么我們自然就很容易想到,根據(jù) maskview 的isa 找到 類對象,在類對象 重寫 hitTest 方法,當hitTestview == self ,返回nil 即可。這樣,事件就別 btn 捕獲到。代碼如下:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

{

? ? UIView *hitTestView = [super hitTest:point withEvent:event];

? ? if (hitTestView == self)

? ? {

? ? ? ? return nil;

? ? }else

? ? {

? ? ? ? return hitTestView;

? ? }

}

2、子視圖超出父視圖 范圍

發(fā)布按鈕已然已經(jīng)超出tabbar的范圍,那么該按鈕是如何響應點擊事件的?

要讓中間按鈕響應點擊超出TabBar按鈕部分的點擊事件,則需要重寫TabBar的hitTest方法了,在執(zhí)行hitTest方法時,判斷點擊區(qū)域在中間按鈕的區(qū)域,則返回中間按鈕,響應該事件,代碼如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

? ? //將當前tabbar的觸摸點轉換坐標系,轉換到中間按鈕的身上,生成一個新的點

? ? CGPoint newP = [self convertPoint:point toView:self.centerBtn];

? ? ? //判斷如果這個新的點是在中間按鈕身上,那么處理點擊事件最合適的view就是中間按鈕

? ? ? if ( [self.centerBtn pointInside:newP withEvent:event])

? ? ? {

? ? ? ? ? ? return self.centerBtn;

? ? ? }

? ? return [super hitTest:point withEvent:event];

}//重寫hitTest方法,去監(jiān)聽中間按鈕的點擊,目的是為了讓凸出的部分點擊也有反應

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容