概念
響應者:對用戶交互動作事件進行響應的對象。
響應者鏈:成為處理事件的響應者的先後順序鏈。
正文
1、Hit-Test 機制
當用戶觸摸(Touch)屏幕進行交互時,系統首先要找到響應者(Responder)。系統檢測到手指觸摸(Touch)操作時,將Touch 以UIEvent的方式加入UIApplication事件隊列中。UIApplication從事件隊列中取出最新的觸摸事件進行分發傳遞到UIWindow進行處理。UIWindow 會通過hitTest:withEvent:方法尋找觸碰點所在的視圖,這個過程稱之為hit-test view。
hitTest 的順序如下
UIApplication -> UIWindow -> Root View -> ··· -> subview
在頂級視圖(Root View)上調用pointInside:withEvent:方法判斷觸摸點是否在當前視圖內;
如果返回NO,那麼hitTest:withEvent:返回nil;
如果返回YES,那麼它會向當前視圖的所有子視圖發送hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢。
如果有subview的hitTest:withEvent:返回非空對象則A返回此對象,處理結束(注意這個過程,子視圖也是根據pointInside:withEvent:的返回值來確定是返回空還是當前子視圖對象的。並且這個過程中如果子視圖的hidden=YES、userInteractionEnabled=NO或者alpha小於0.1都會並忽略);
如果所有subview遍歷結束仍然沒有返回非空對象,則hitTest:withEvent:返回self;
系統就是這樣通過hit test找到觸碰到的視圖(Initial View)進行響應。
2、響應者鏈 (Responder Chain)
有些時候,Touch後系統通過hit test 機制找到了觸碰到的Initial View,但是Initial view並沒有或者無法正常處理此次Touch。這個時候,系統便會通過響應者鏈尋找下一個響應者,以對此次Touc 進行響應。
響應者鏈順序如下:
Initial View -> View Controller(如果存在) -> superview -> · ·· -> rootView -> UIWindow -> UIApplication
示意圖
如果一個View有一個視圖控制器(View Controller),它的下一個響應者是這個視圖控制器,緊接著才是它的父視圖(Super View),如果一直到Root View都沒有處理這個事件,事件會傳遞到UIWindow(iOS中有一個單例Window),此時Window如果也沒有處理事件,便進入UIApplication,UIApplication是一個響應者鏈的終點,它的下一個響應者指向nil,以結束整個循環。
3、實際開發中常見的相關問題
在實際開發中,經常會遇到視圖沒有響應的情況,特別是新手會經常搞不清楚狀況。
一下是視圖沒有響應的幾個情況:
1.userInteractionEnabled=NO;
2.hidden=YES;
3.alpha=0~0.01;
4.沒有實現touchesBegan:withEvent:方法,直接執行touchesMove:withEvent:等方法;
5.目標視圖點擊區域不在父視圖的Frame上 (superView背景色為clear Color的時候經常會忽略這個問題)。
在某些情景下,我們在點擊子視圖的時候仍然需要調用父視圖的touchesBegan:withEvent:等方法,例如我們在父視圖上添加了一個覆蓋范圍了父視圖大部分面積的TableView或ScrollerView 或其他View,而我們要通過父視圖的touchesBegan:withEvent:方法來收鍵盤。
這個時候我們可以通過UIView的類別,重寫touch相關方法,代碼如下:
-(void)setEnableNextResponder:(BOOL)enableNextResponder { objc_setAssociatedObject(self, &enableNextResponderKey, enableNextResponderKey, OBJC_ASSOCIATION_ASSIGN); } -(BOOL)enableNextResponder { return objc_getAssociatedObject(self, &enableNextResponderKey); } -(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{ if (self.enableNextResponder) { [[self nextResponder] touchesBegan:touches withEvent:event]; [super touchesBegan:touches withEvent:event]; } } -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (self.enableNextResponder) { [[self nextResponder] touchesEnded:touches withEvent:event]; [super touchesEnded:touches withEvent:event]; } } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (self.enableNextResponder) { [[self nextResponder] touchesMoved:touches withEvent:event]; [super touchesMoved:touches withEvent:event]; } } -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { if (self.enableNextResponder) { [[self nextResponder] touchesCancelled:touches withEvent:event]; [super touchesCancelled:touches withEvent:event]; } }
我在我的github:WXSTools 放了個平時寫來自己用的部分工具類的集合,裡面的UIView+Touch可以作為Demo代碼。
本文作者: 伯樂在線 - alanwangmodify