- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
UIWindow會經過調用hitTest:withEvent:方法(這個方法會對UIWindow的全部子view調用pointInside:withEvent:方法,其中返回的YES的視圖爲ViewA,得知用戶點擊的範圍在ViewA中),相似地,在ViewA中調用hitTest:withEvent:方法,得知用戶點擊的範圍在ViewB中,依此類推,最終找到點擊的視圖爲ViewE。html
其中,hitTest:withEvent:方法大體的實現以下:app
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ for (UIView *view in self.subviews) { if([view pointInside:point withEvent:event]){ UIView *hitTestView = [view hitTest:point withEvent:event]; if(nil == hitTestView){ return view; } } } return nil; }
經過以上遞歸的形式就能找到用戶點擊的是哪一個view,其中還要注意的是當前的view是否開啓了userIntercationEnabled屬性,若是這個屬性未開啓,以上遞歸也會在未開啓userIntercationEnabled屬性的view層終止。ide
3、如何根據響應鏈響應post
既然找到了用戶點擊的view,那麼當前就應該響應用戶的點擊事件了,UIView與UIViewController的共同父類是UIResponder,他們均可以複寫下列4個方法:url
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
這個響應點擊事件的過程是上面的逆序操做,這就是用到了UIResponder的nextResponder方法了。好比上面的圖點擊ViewE,這時候ViewE先響應,接下來是nextResponder即ViewB,接下來是ViewB的nextResponder即ViewA。spa
關於nextResponder有以下幾條規則:.net
1. 當一個view調用其nextResponder會返回其superView;
2. 若是當前的view爲UIViewController的view被添加到其餘view上,那麼調用nextResponder會返回當前的UIViewController,而這個UIViewController的nextResponder爲view的superView;
3. 若是當前的UIViewController的view沒有添加到任何其餘view上,當前的UIViewController的nextResponder爲nil,無論它是keyWinodw或UINavigationController的rootViewController,都是如此;
4. 若是當前application的keyWindow的rootViewController爲UINavigationController(或UITabViewController),那麼經過調用UINavigationController(或UITabViewController)的nextResponder獲得keyWinodw;
5. keyWinodw的nextResponder爲UIApplication,UIApplication的nextResponder爲AppDelegate,AppDelegate的nextResponder爲nil。
用圖來表示,以下所示:code
4、遇到的問題htm
在開發過程當中,咱們有可能遇到UIScrollView 或 UIImageView 截獲touch事件,致使touchesBegan: withEvent:/touchesMoved: withEvent:/touchesEnded: withEvent: 等方法不執行。好比下面這種狀況,scrollView的superView是view,view對應的viewController中的touchesBegan: withEvent:/touchesMoved: withEvent:/touchesEnded: withEvent: 等方法就不執行。blog
@implementation UIScrollView (Touch) - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { if([self isMemberOfClass:[UIScrollView class]]) { [[self nextResponder] touchesBegan:touches withEvent:event]; } } @end
這樣UIScrollView確實會用nextResponder把響應傳遞到view,接下來傳遞到viewController中。可是,若是沒有使用if([self isMemberOfClass:[UIScrollView class]]) 進行過濾判斷,那麼,有可能會致使一個使用系統手寫輸入法時帶來的crash問題。即手寫的鍵盤的子view是UIKBCandidateCollectionView,調用了[[self nextResponder] touchesBegan:touches withEvent:event];後會形成系統的crash問題:
-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance 0x104f6c6b0
這個crash的復現見《UIKBBlurredKeyView candidateList:unrecognized...BUG修復》,它的解決辦法也隨處可見,好比《-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance 0x5a89960》
此crash的技術層面詳細緣由:
手寫的鍵盤的子view是UIKBCandidateCollectionView(UIColloectionView的子類)的實例,它的nextResponder是UIKBHandwritingCandidateView類型的實例,執行UIKBHandwritingCandidateView的touchesBegan:withEvent:方法後,會使得整個candidate view呈選中狀態,而蘋果對手寫鍵盤的選擇candidate字符時的原生處理方法是會避免candidate view呈選中狀態的。整個candidate view呈選中狀態後後再點擊鍵盤的任意地方,本應調用UIKBCandidateView實例的方法candidateList,結果調用了UIKBBlurredKeyView的candidateList方法,致使方法找不到,致使"-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance "crash。
crash總結:
經過對這個crash的詳細分析,雖然上面的使用isMemberOfClass判斷後使用nextResponder對事件響應鏈進行傳遞沒有問題,但因爲nextResponder依然具備不可控性,仍是不建議用category複寫系統的方法,這一點之後必定注意。
謹慎使用Category,特別是覆蓋系統原始方法的category的實現。
(PS:本文參考文章《關於響應者鏈》)