響應鏈
響應鏈由一個個UIResponder的子類構成的,UIResponder是系統一個負責接受和處理事件的類。
一般情況下,一條響應鏈開始于第一響應者,結束于application對象。
那么是如何確定第一響應者的?
響應鏈的工作原理
拿我們平常點擊某一應用的按鈕并跳轉到另一個頁面為例,這其中對事件的處理分為兩步:
1、事件的傳遞,找到第一響應者。
2、事件的響應,由第一響應者處理事件,執行跳轉到另一頁。
1、 事件的傳遞分發流程:
1.點擊時會產生事件UIEvent并存入UIApplication中的事件隊列中, 并且在整個視圖結構中自上而下的進行分發
2.UIWindow接受到事件開始進行最優響應視圖查詢的過程。
3.當到UIViewController這一層時同樣對其根視圖(self.view及其上subviews)開始最優響應視圖查詢。
該過程會調用UIView的兩個方法,判斷點擊的位置是否在當前視圖上。
//判斷當前點擊事件的最優響應者,也是第一響應者。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判斷當前點擊是否在視圖內
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
流程如下:
1.調用hitTest方法進行最優響應視圖查詢,以上三種情況會使該方法返回nil,即當前視圖下無最優響應視圖
hidden = YES
userInteractionEnabled = NO
alpha < 0.01
2.hitTest方法內部會調用pointInside方法對點擊點進行是否在當前視圖bounds內進行判斷,如果超出bounds,hitTest則返回nil,未超出范圍則進行步驟3
3.對當前視圖下的subviews采取逆序上述1 2步驟查詢最優響應視圖。如果hitTest返回了對應視圖則說明在當前視圖層級下有最優響應視圖。
下面是最優命響應圖查詢代碼示例:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.alpha < 0.01 || !self.userInteractionEnabled || self.hidden) {
return nil;
}
if (![self pointInside:point withEvent:event]) {
return nil;
}
__block UIView *hitView = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
hitView = [subview hitTest:point withEvent:event];
if (hitView) {
*stop = YES;
}
}];
return hitView ? : self;
}
事件的分發要點:
1、 UIApplication開始自上而下的進行事件分發
2、 UIView內部開始反向遍歷查找最優視圖
2、事件的響應流程
1.判斷最優響應視圖能否響應事件,如果視圖能進行響應則事件在響應鏈中的傳遞終止。如果視圖不能響應則將事件傳遞給 nextResponder也就是通常的superview進行事件響應
2.如果事件繼續上報至UIWindow并且無法響應,它將會把事件繼續上報給UIApplication
3.如果事件繼續上報至UIApplication并且也無法響應,它將會將事件上報給其Delegate,但前提下這個Delegate不屬于 響應鏈 并且是UIResponder的子類
4.如果最終事件依舊未被響應則會被系統拋棄
事件響應要點:
事件響應自下而上進行上報