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

示意圖說明:白色 view 是藍(lán)色 view 的父視圖;藍(lán)色 view 是橙色 view 的父視圖。
-
需求一:點擊重疊區(qū),只有藍(lán)色 view(既父視圖)響應(yīng)事件。
一個最簡單的辦法是將子視圖的
isUserInteractionEnabled設(shè)置為false;也可以在子視圖的hitTest(_:with:)方法里面返回nil或superview,可以達(dá)到同樣的效果。
-
需求二:點擊屏幕上的任意地方;只有藍(lán)色 view 響應(yīng)事件。
一個最簡單的辦法是在藍(lán)色 view 的
hitTest(_:with:)方法里返回self。當(dāng)事件傳遞到藍(lán)色 view 時,返回自己做為最適合觸發(fā)事件的控件。
-
需求三:點擊橙色 view 的任意地方,藍(lán)色 view(既父視圖)響應(yīng)事件。
難點在于點擊非重疊區(qū)時,藍(lán)色 view 不能接收到事件。為什么會出現(xiàn)這種情況呢?回顧一下 “原理篇 - 如何尋找最適合的控件來處理事件” 就會發(fā)現(xiàn),一個控件想要接收事件需要滿足兩個條件:
- 判斷自己能否觸發(fā)事件;
-
判斷觸摸點是否在自己身上(
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 接收事件。)
-
需求四:點擊重疊區(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) }
-
需求五:正常響應(yīng),點擊橙色 view 是橙色 view 響應(yīng)事件;而點擊藍(lán)色 view 是藍(lán)色 view 響應(yīng)事件。
可以說是經(jīng)常出現(xiàn)的需求了,有時候我們需要處理超出父視圖區(qū)域的子視圖事件,但是點擊超出區(qū)域的部分卻不能響應(yīng)事件。那要怎么做呢?
其實這個問題在需求三的第一個示例中已經(jīng)解決了,這里不再贅述。