一個99%剛入職場的iOSer踩過的坑
小胡剛入職的第二天,iOS組長給他一個實習任務:用UICollectionView搭一個商品選擇介面。小胡覺著這不難啊,估摸著半小時就能搭完頁面,除錯除錯,對接下介面,半天也完成了。
故事總是這樣,從喜劇開始,最終變成了一部懸疑劇:
大半天過去了,小胡惴惴不安地找到組長:“我遇到一個詭異的問題...”
組長:“什麼問題?”
“UICollectionView的didSelectItemAtIndexPath怎麼點都不響應!”
“哦?我來看看”,說著,組長開啟“檢視層次檢視器”,結果發現,UICollectionView 被加到一個全屏的UIImageView背景上,而UIImageView的userInteractionEnabled預設為NO !!!
“這樣你當然什麼觸控事件都接收不到了呀!”
小胡羞的滿臉通紅,感覺這個失誤和沒插電源,卻說電腦壞了一樣愚蠢,不過他還是說出了心中的疑問:組長,我明明點選的是UICollectionView,為什麼觸控事件卻被父檢視給遮蔽了呢?
組長看著小胡,“看來,得給你理一理響應者鏈了”:
響應者鏈有兩個過程
響應者鏈是iOS處理“事件”的一種機制,我們可以簡單理解為觸控事件在類中傳遞的過程,而構成iOS介面上的幾乎所有類,UIApplication、UIView、UIViewController 都是直接繼承自UIResponder,所以當用戶點選屏幕後,由哪個UIResponder來處理這個點選,就需要進行一番選擇。
一個螢幕上,UIResponder 被組織成了一棵樹:

UIResponder結構樹
命中測試
,是由hitTest 方法完成的:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
目的是為了找到點擊發生在哪個UIResponder的範圍內,即所謂的“命中者”。
覆蓋 hitTest 時,要呼叫 [super hitTest:point withEvent:event]
,否則鏈條被中斷,子檢視將不會執行hitTest,也就無法參與到響應者鏈中來。
找到命中者後,就要在這些命中者中找個“響應者”,響應者是有“應聘條件”的:
- userInteractionEnabled不能為NO,注意,userInteractionEnabled 具有傳遞作用,即父檢視為NO,則子檢視的 hitTest 都通不過。
- hidden 必須為NO,即不能被隱藏。
- alpha不能低於 0.01,也就是不能幾乎透明。
這個過程是與第一個過程相反的,由 子檢視
向上傳遞:

尋找響應者的過程
第一個滿足以上條件的命中者,如果實現
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
就被確認為響應者,系統認為事件被正確的處理了,事件傳遞過程 到此結束
。
命中者中找不到響應者,怎麼辦?事件就會被一直傳遞,直到遍歷了UIResponder 樹上全部結點,最後由UIWindow做最後處理。
響應者鏈作用
- 可以讓一個觸控事件發生的時候讓多個響應者(在同一條響應鏈上)同時響應該事件: 通過呼叫
[super touchesXXX]
。 - 在需要控制全域性的使用者點選事件的時候,可以通過 UIApplication 的
beginIgnoringInteractionEvents
和endIgnoringInteractionEvnets
來關閉或者開啟事件傳遞。 - 可以重寫
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
來擴大UIButton的響應範圍(當然縮小也可以)。
-
當目標檢視點選區域不在父檢視的Frame中時,也能響應事件, 比如鹹魚的tabbar
凸起效果的Tabbar
這裡給一個處理的示例程式碼:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.clipsToBounds && !self.hidden && self.alpha > 0) { for (UIView *subview in self.subviews.reverseObjectEnumerator) { CGPoint subPoint = [subview convertPoint:point fromView:self]; UIView *result = [subview hitTest:subPoint withEvent:event]; if (result != nil) { return result; } } } return nil; }
小結
響應者鏈是點選事件命中和分配給響應者的過程,它把觸控命中者組成一個鏈條,讓開發人員有機會介入事件的響應過程,從而更靈活的處理點選等事件。
思考題
如何編寫hitTest,使得當前檢視不響應點選,又不影響子檢視響應事件呢?期待你的留言,只有動手寫過的程式碼,才是自己的!