基本概念
響應者:
在iOS中,響應者為能響應事件的UIResponder子類對象,如UIButton、UIView等。
響應鏈:
響應鏈是由鏈接在一起的響應者(UIResponse子類)組成的。默認情況下,響應鏈是由第一響應者,到application對象以及中間所有響應者一起組成的。
事件傳遞:
獲得響應鏈后,將事件由第一響應者往application傳遞的過程即為事件傳遞。
響應者鏈執行的過程
1、尋找第一響應者
順着視圖樹結構,由根節點開始遍歷所有的子控件,通過hitTest:withEvent:
方法找到第一響應者,從第一響應者到application對象的一系列響應者即為響應鏈
2、尋找最終響應對象
將響應事件從第一響應者順着響應鏈傳遞,如果第一響應者無法響應將繼續向父控件傳遞,直到找到最終響應對象。
如何尋找第一響應者
1、當iOS程序發生觸摸事件后,系統會利用Runloop將事件加入到UIApplication的任務隊列中,具體過程可以參考深入理解RunLoop
2、UIApplication分發觸摸事件到UIWindow,然后UIWindow依次向下分發給UIView
3、UIView調用hitTest:withEvent:
方法看看自己能否處理事件,以及觸摸點是否在自己上面。
4、如果滿足條件,就遍歷UIView上的子控件。重復上面的動作。
5、直到找到最頂層的一個滿足條件(既能處理觸摸事件,觸摸點又在上面)的子控件,此子控件就是我們需要找到的第一響應者。
hitTest:withEvent:的處理流程
(上面的查找其實就是由該方法遞歸調用實現的)
1、首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內;
2、若返回NO,則hitTest:withEvent:返回nil;
3、若返回YES,則向當前視圖的所有子視圖(subviews)發送hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢;
4、若第一次有子視圖返回非空對象,則hitTest:withEvent:方法返回此對象,處理結束;
5、如所有子視圖都返回非,則hitTest:withEvent:方法返回自身(self)。
hitTest:withEvent:方法的偽代碼
//返回最適合處理事件的視圖,最好在父視圖中指定子視圖的響應
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.userInteractionEnabled || !self.hidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
CGPoint subPoint = [subView convertPoint:point fromView:self];
UIView *bestView = [subView hitTest:subPoint withEvent:event];
if (bestView) {
return bestView;
}
}
return self;
}
return nil;
}
事件的響應流程
通過上面的 hitTest:withEvent:
尋找到第一響應者后,需要逆着尋找第一響應者的方向(從第一響應者->UIApplication)來響應事件。
流程如下
1.首先通過 hitTest:withEvent:
確定第一響應者,以及相應的響應鏈
2.判斷第一響應者能否響應事件,如果第一響應者能進行響應則事件在響應鏈中的傳遞終止。如果第一響應者不能響應則將事件傳遞給 nextResponder也就是通常的superview進行事件響應
3.如果事件繼續上報至UIWindow並且無法響應,它將會把事件繼續上報給UIApplication
4.如果事件繼續上報至UIApplication並且也無法響應,它將會將事件上報給其Delegate
5.如果最終事件依舊未被響應則會被系統拋棄
需要思考的點
為什么會出現在響應鏈中,但是無法響應事件。
- 這里容易產生誤解,就是尋找第一響應者的過程其實只判斷該視圖是否具有響應觸摸事件的能力,但是並沒有判斷是否可以響應該event。
- 第一響應者對event的具體處理,是在事件響應的過程中進行判定的。
視圖不響應檢查要點
- hidden = YES 視圖被隱藏
- userInteractionEnabled = NO 不接受響應事件
- alpha <= 0.01,透明視圖不接收響應事件
- 子視圖超出父視圖范圍
- 需響應視圖被其他視圖蓋住
- 是否重寫了其父視圖以及自身的hitTest方法
- 是否重寫了其父視圖以及自身的pointInside方法
參考文章
iOS事件響應鏈傳遞的一些理解
Using Responders and the Responder Chain to Handle Events
iOS響應鏈和傳遞機制