1. 程式人生 > >iOS 事件穿透,點選穿透,控制元件不響應事件

iOS 事件穿透,點選穿透,控制元件不響應事件

前言

小夥伴們在開發中是否遇到過這樣的需求呢,一個控制元件的某個部分被另外一個控制元件遮擋住,當點選這個重疊部分時,需要響應被遮蓋控制元件的點選事件,就如下圖所示

當我們點選區域3時,響應藍色按鈕的點選事件,點選區域1和2時,響應紅色按鈕的點選事件,對於區域1和3沒什麼好說的,那如何讓紅色按鈕響應區域2的點選呢?這就是筆者今天要講的內容。

事件傳遞

大家應該都知道,事件從應用程式開始,按照從上到下的順序(UIApplication -> UIWindow -> rootViewController -> ...)一級一級傳遞,並且系統在尋找最適合處理事件的控制元件時,是從後往前遍歷子控制元件的(網上資料太多,不做詳細闡述,請自行百度)

上圖中藍色按鈕在紅色按鈕之後新增,當系統尋找最適合的控制元件時,藍色按鈕在紅色按鈕之前被找到,系統發現藍色按鈕很適合處理事件,所以方法便返回了,紅色按鈕就沒有了處理事件的機會。

系統如何尋找最適合控制元件

  • 判斷自己能否接受觸控事件,如果不能,返回nil
  • 判斷觸控點是否在自己身上,如果不能,返回nil
  • 從後往前遍歷子控制元件,重複上面的步驟,如果沒有適合的子控制元件,返回自己

我們來看看系統內部是如何實現的,筆者這裡自定義了一個UIWindow,讓它成為主視窗,並重寫它的hitTest方法,執行之後,其事件處理功能,與系統的類似,所以系統內部大概就是這樣實現的

當一個控制元件的透明度小於某個值時,就不再響應事件,上圖中0.01僅僅是為了測試,並非準確的值,要注意的就是,對於繼承自UIControl的控制元件,還需要判斷enable的值

事件穿透

既然系統尋找最合適控制元件的方法滿足不了我們,那我們就重寫系統的方法

思路
  • 點選藍色按鈕的區域2,紅色按鈕響應事件,那肯定要重寫藍色按鈕的hitTest方法
  • 在hitTest方法中,將觸控點的座標系從藍色按鈕轉換到紅色按鈕上,即以紅色按鈕左上角為原點
  • 座標系轉換後,判斷觸控點是否在紅色按鈕上,如果是,直接返回紅色按鈕(嚴謹一點的做法是呼叫紅色按鈕的hitTest方法),如果不是,那就呼叫系統的方法,讓系統去處理

有了思路,那萬事具備只欠東風了,接下來上東風

新建一個類,繼承自UIButton,筆者這裡直接命名為BlueButton,修改sb\xib中藍色按鈕的型別為BlueButton

將紅色按鈕連線到BlueButton.m檔案中,不用試了,直接連是連不了的,我們可以先在BlueButton.m中定義一個屬性,前面加上IBOutlet,然後單擊圖中的空心圓,拖到紅色按鈕上就OK了

最後,在BlueButton.m中重寫藍色按鈕的hitTest方法,程式碼如下

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint redBtnPoint = [self convertPoint:point toView:_redButton];
    if ([_redButton pointInside:redBtnPoint withEvent:event]) {
        return _redButton;
    }
    //如果希望嚴謹一點,可以將上面if語句及裡面程式碼替換成如下程式碼
    //UIView *view = [_redButton hitTest: redBtnPoint withEvent: event];
    //if (view) return view;
    return [super hitTest:point withEvent:event];
}

來看執行結果,點選區域2時,紅色按鈕高亮並響應事件