注:根據史上最詳細的iOS之事件的傳遞和響應機制-實踐篇重新整理(適當刪減及補充)。
示意圖說明:白色 view 是藍色 view 的父檢視;藍色 view 是橙色 view 的父檢視。
-
需求一:點選重疊區,只有藍色 view(既父檢視)響應事件。
一個最簡單的辦法是將子檢視的
isUserInteractionEnabled
設定為false
;也可以在子檢視的hitTest(_:with:)
方法裡面返回nil
或superview
,可以達到同樣的效果。
-
需求二:點選螢幕上的任意地方;只有藍色 view 響應事件。
一個最簡單的辦法是在藍色 view 的
hitTest(_:with:)
方法裡返回self
。當事件傳遞到藍色 view 時,返回自己做為最適合觸發事件的控制元件。
-
需求三:點選橙色 view 的任意地方,藍色 view(既父檢視)響應事件。
難點在於點選非重疊區時,藍色 view 不能接收到事件。為什麼會出現這種情況呢?回顧一下 “原理篇 - 如何尋找最適合的控制元件來處理事件” 就會發現,一個控制元件想要接收事件需要滿足兩個條件:
- 判斷自己能否觸發事件;
- 判斷觸控點是否在自己身上(
point(inside:with:)
)。
根據第二點,我們在點選非重疊區時,觸控點不在自己(藍色 view)身上,因此不能夠接收事件。
再回顧一下這一節的要點:觸控事件傳遞的過程是從父控制元件傳遞到子控制元件的,如果父控制元件也不能接收事件,那麼子控制元件就不可能接收事件。
那應該怎麼做呢?關鍵還是在第二點上(判斷觸控點是否在自己身上),這個方法返回的是一個
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 響應事件。
可以說是經常出現的需求了,有時候我們需要處理超出父檢視區域的子檢視事件,但是點選超出區域的部分卻不能響應事件。那要怎麼做呢?
其實這個問題在需求三的第一個示例中已經解決了,這裡不再贅述。