注:根據史上最詳細的iOS之事件的傳遞和響應機制-實踐篇從新整理(適當刪減及補充)。swift
示意圖說明:白色 view 是藍色 view 的父視圖;藍色 view 是橙色 view 的父視圖。ide
需求一:點擊重疊區,只有藍色 view(既父視圖)響應事件。ui
一個最簡單的辦法是將子視圖的 isUserInteractionEnabled
設置爲 false
;也能夠在子視圖的 hitTest(_:with:)
方法裏面返回 nil
或 superview
,能夠達到一樣的效果。spa
需求二:點擊屏幕上的任意地方;只有藍色 view 響應事件。3d
一個最簡單的辦法是在藍色 view 的 hitTest(_:with:)
方法裏返回 self
。當事件傳遞到藍色 view 時,返回本身作爲最適合觸發事件的控件。code
需求三:點擊橙色 view 的任意地方,藍色 view(既父視圖)響應事件。cdn
難點在於點擊非重疊區時,藍色 view 不能接收到事件。爲何會出現這種狀況呢?回顧一下 「原理篇 - 如何尋找最適合的控件來處理事件」 就會發現,一個控件想要接收事件須要知足兩個條件:blog
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 接收事件。)
需求四:點擊重疊區時,橙色 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)
}
複製代碼
需求五:正常響應,點擊橙色 view 是橙色 view 響應事件;而點擊藍色 view 是藍色 view 響應事件。
能夠說是常常出現的需求了,有時候咱們須要處理超出父視圖區域的子視圖事件,可是點擊超出區域的部分卻不能響應事件。那要怎麼作呢?
其實這個問題在需求三的第一個示例中已經解決了,這裏再也不贅述。