iOS 中的事件傳遞和響應機制 - 實踐篇

注:根據史上最詳細的iOS之事件的傳遞和響應機制-實踐篇從新整理(適當刪減及補充)。swift

需求場景示意圖

示意圖說明:白色 view 是藍色 view 的父視圖;藍色 view 是橙色 view 的父視圖。ide

  1. 需求一:點擊重疊區,只有藍色 view(既父視圖)響應事件。ui

    一個最簡單的辦法是將子視圖的 isUserInteractionEnabled 設置爲 false ;也能夠在子視圖的 hitTest(_:with:) 方法裏面返回 nilsuperview ,能夠達到一樣的效果。spa


  2. 需求二:點擊屏幕上的任意地方;只有藍色 view 響應事件。3d

    一個最簡單的辦法是在藍色 view 的 hitTest(_:with:) 方法裏返回 self 。當事件傳遞到藍色 view 時,返回本身作爲最適合觸發事件的控件。code


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

    難點在於點擊非重疊區時,藍色 view 不能接收到事件。爲何會出現這種狀況呢?回顧一下 「原理篇 - 如何尋找最適合的控件來處理事件」 就會發現,一個控件想要接收事件須要知足兩個條件:blog

    1. 判斷本身可否觸發事件;
    2. 判斷觸摸點是否在本身身上point(inside:with:) )。

    根據第二點,咱們在點擊非重疊區時,觸摸點不在本身(藍色 view)身上,所以不可以接收事件。事件

    再回顧一下這一節的要點:觸摸事件傳遞的過程是從父控件傳遞到子控件的,若是父控件也不能接收事件,那麼子控件就不可能接收事件。get

    那應該怎麼作呢?關鍵仍是在第二點上(判斷觸摸點是否在本身身上),這個方法返回的是一個 Bool 類型的值,換句話說,不管點是否在本身身上,只要讓這個方法返回 true,就可讓藍色 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 也實現了 touches(_:with:) ,這時候是橙色 view 觸發事件而不是藍色 view。爲何呢?

    由於只要判斷符合了條件,事件就會傳遞到橙色 view,而觸摸點正好在橙色 view 身上,所以是橙色 view 觸發了事件。

    不過通常來講,有這種需求的子控件(橙色 view)都不會本身實現事件而是交給父控件(藍色 view)去處理。因此若是不想考慮這麼多的話,能夠直接用上面的方法。可是若是想屏蔽掉子控件事件的觸發的話,仍是有辦法解決的。

    解決的辦法就是攔截橙色 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 的身上,返回本身(藍色 view),不在則正常返回。
        // 注:此時的 subviews.first 表明橙色 view。
        return subviews.first!.frame.contains(point) ? self : hitView
    }
    複製代碼

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


  4. 需求四:點擊重疊區時,橙色 view 和藍色 view 都響應事件。

    一個最簡單的辦法是在咱們從新實現橙色 view 的 touches(_:with:) 方法後,調用 super.touches(_:with:) 讓它繼續將事件傳遞給下一個響應者(藍色 view)接收並處理事件。

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

  5. 需求五:正常響應,點擊橙色 view 是橙色 view 響應事件;而點擊藍色 view 是藍色 view 響應事件。

    能夠說是常常出現的需求了,有時候咱們須要處理超出父視圖區域的子視圖事件,可是點擊超出區域的部分卻不能響應事件。那要怎麼作呢?

    其實這個問題在需求三的第一個示例中已經解決了,這裏再也不贅述。

相關文章
相關標籤/搜索