iOS 中的事件傳遞和響應(yīng)機制 - 實踐篇

注:根據(jù)史上最詳細(xì)的iOS之事件的傳遞和響應(yīng)機制-實踐篇重新整理(適當(dāng)刪減及補充)。

需求場景示意圖

示意圖說明:白色 view 是藍(lán)色 view 的父視圖;藍(lán)色 view 是橙色 view 的父視圖。

  1. 需求一:點擊重疊區(qū),只有藍(lán)色 view(既父視圖)響應(yīng)事件。

    一個最簡單的辦法是將子視圖的 isUserInteractionEnabled 設(shè)置為 false ;也可以在子視圖的 hitTest(_:with:) 方法里面返回 nilsuperview ,可以達(dá)到同樣的效果。


  2. 需求二:點擊屏幕上的任意地方;只有藍(lán)色 view 響應(yīng)事件。

    一個最簡單的辦法是在藍(lán)色 view 的 hitTest(_:with:) 方法里返回 self 。當(dāng)事件傳遞到藍(lán)色 view 時,返回自己做為最適合觸發(fā)事件的控件。


  3. 需求三:點擊橙色 view 的任意地方,藍(lán)色 view(既父視圖)響應(yīng)事件。

    難點在于點擊非重疊區(qū)時,藍(lán)色 view 不能接收到事件。為什么會出現(xiàn)這種情況呢?回顧一下 “原理篇 - 如何尋找最適合的控件來處理事件” 就會發(fā)現(xiàn),一個控件想要接收事件需要滿足兩個條件:

    1. 判斷自己能否觸發(fā)事件;
    2. 判斷觸摸點是否在自己身上point(inside:with:) )。

    根據(jù)第二點,我們在點擊非重疊區(qū)時,觸摸點不在自己(藍(lán)色 view)身上,因此不能夠接收事件。

    再回顧一下這一節(jié)的要點:觸摸事件傳遞的過程是從父控件傳遞到子控件的,如果父控件也不能接收事件,那么子控件就不可能接收事件。

    那應(yīng)該怎么做呢?關(guān)鍵還是在第二點上(判斷觸摸點是否在自己身上),這個方法返回的是一個 Bool 類型的值,換句話說,無論點是否在自己身上,只要讓這個方法返回 true,就可以讓藍(lán)色 view 接收事件。

    /// BlueView.swift
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        // 首先正常返回,
        // 如果點不在自己身上,則判斷點是否在橙色 view 身上。
        // 注:此時的 subviews.first 代表橙色 view。
        return super.point(inside: point, with: event) || subviews.first!.frame.contains(point)
    }
    

    這樣做是可以的,也最簡單。但有一個問題,那就是如果橙色 view 也實現(xiàn)了 touches(_:with:) ,這時候是橙色 view 觸發(fā)事件而不是藍(lán)色 view。為什么呢?

    因為只要判斷符合了條件,事件就會傳遞到橙色 view,而觸摸點正好在橙色 view 身上,因此是橙色 view 觸發(fā)了事件。

    不過一般來說,有這種需求的子控件(橙色 view)都不會自己實現(xiàn)事件而是交給父控件(藍(lán)色 view)去處理。所以如果不想考慮這么多的話,可以直接用上面的方法。但是如果想屏蔽掉子控件事件的觸發(fā)的話,還是有辦法解決的。

    解決的辦法就是攔截橙色 view 接收事件,只要在 BlueView.swift 中重寫 hitTest(_:with) 方法,返回指定的 view 來做為最適合處理事件的控件就可以了。

    /// BlueView.swift
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let hitView = super.hitTest(point, with: event)
        // 如果點在橙色 view 的身上,返回自己(藍(lán)色 view),不在則正常返回。
        // 注:此時的 subviews.first 代表橙色 view。
        return subviews.first!.frame.contains(point) ? self : hitView
    }
    

    這樣一來,事件就不會傳遞到橙色 view 了,只要點在橙色 view 身上,我就返回它的父視圖(藍(lán)色 view);如果不在,就正常返回(點擊了藍(lán)色 view 還是藍(lán)色 view 觸發(fā)事件;點擊了白色 view 則觸摸點不在藍(lán)色 view 身上,此時白色 view 接收事件。)


  4. 需求四:點擊重疊區(qū)時,橙色 view 和藍(lán)色 view 都響應(yīng)事件。

    一個最簡單的辦法是在我們重新實現(xiàn)橙色 view 的 touches(_:with:) 方法后,調(diào)用 super.touches(_:with:) 讓它繼續(xù)將事件傳遞給下一個響應(yīng)者(藍(lán)色 view)接收并處理事件。

    /// OrangeView.swift
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("Orange: \(#function)")
        // 繼續(xù)將事件傳遞給下一個響應(yīng)者 (此時是藍(lán)色 view)
        super.touchesBegan(touches, with: event)
    }
    
    /// BlueView.swift
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("Blue", #function)
    }
    

  5. 需求五:正常響應(yīng),點擊橙色 view 是橙色 view 響應(yīng)事件;而點擊藍(lán)色 view 是藍(lán)色 view 響應(yīng)事件。

    可以說是經(jīng)常出現(xiàn)的需求了,有時候我們需要處理超出父視圖區(qū)域的子視圖事件,但是點擊超出區(qū)域的部分卻不能響應(yīng)事件。那要怎么做呢?

    其實這個問題在需求三的第一個示例中已經(jīng)解決了,這里不再贅述。

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容