響應鏈工作原理
點擊某一控件到其響應相關事件其實是分為兩步:事件的傳遞與事件的響應
- 事件分發與傳遞:自上而下
- 事件響應:自下而上
UIView和CALayer
- UIView 為其提供內容,以及負責處理觸摸等事件,參與響應鏈
- CALayer 負責顯示的內容contents
tips:使用了單一職責原則
layer是由CALayer的contents決定的,backing store其實就是位圖
backgroundColor是對CALayer同名屬性方法的包裝
事件傳遞與視圖響應機制
/**
* 返回最終響應事件的視圖
* @param point
* @param event
* @by 打碟的DJ
*/
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
/**
* 用來判斷某一個點擊的位置是否在當前視圖范圍內
* @param point
* @param event
* @by 打碟的DJ
*/
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds
事件傳遞的流程
- 點擊屏幕的某一個視圖(位置)
- 事件首先傳遞給UIApplication
- 在由UIApplication傳遞給當前的UIWindow
- UIWindow中會通過 hitTest: withEvent: 方法來判斷返回響應的視圖
- 在上訴方法系統實現的內部會調用 pointInside: withEvent: 方法來判斷當前點擊的位置是否在UIWindow范圍內
- 如果在的話,會在UIWindow中遍歷它的子視圖(subViews),找到響應的視圖(已倒序的方法進行遍歷)
- 在每個子視圖中都會調用 hitTest: withEvent: 來判斷是否有響應的視圖
- 如果有返回的響應的視圖則響應點擊的事件,如果沒有,則不發生任何改變
hitTest: withEvent: 系統實現流程圖
首先在 hitTest: withEvent: 方法中會判斷當前視圖的hidden屬性、是否可交互(userInteractionEnabled)、透明度(alpha)是否大于0.01
如果當前視圖不是隱藏的、可交互并且透明度大于0.01,才會走后續的流程,否則返回nil
如果都通過了,則會調用pointInside: withEvent:方法來判斷點擊的當前位置是否在當前視圖范圍內
-
如果返回的是YES,則倒序遍歷當前視圖的所有子視圖,遍歷的過程中會調動所有子視圖的hitTest: withEvent: 方法,如果遍歷過程中有返回點擊的視圖,則結束遍歷,當前視圖響應事件,如果沒有返回,則遍歷當前視圖的同級視圖,直到找到對應的響應事件的視圖,如果遍歷結束后未找到響應事件的視圖,則返回nil,不發生任何變化
/** * 用來判斷控件是否接受事件以及找到最合適的view * @param point * @param event * @by 打碟的DJ */ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { //先判斷當前視圖的hidden屬性,是否可交互,透明度是否大于0.01 if (self.hidden || !self.userInteractionEnabled || self.alpha <= 0.01) { return nil; } //判斷點擊位置是否在當前視圖中,在則倒序遍歷子視圖 if ([self pointInside:point withEvent:event]) { __block UIView *hit = nil; [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //坐標轉換 CGPoint convertedPoint = [obj convertPoint:point fromView:self]; //調用子視圖的hitTest:withEvent方法 hit = [obj hitTest:convertedPoint withEvent:event]; //如果找到了接收事件的對象,則停止遍歷 if (hit) { *stop = YES; } }]; return hit; } return nil; }
響應鏈
響應鏈 其實是由一個個UIResponder的子類構成的,UIResponder是系統一個負責接受和處理事件的類。
- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event;
以上這幾個響應觸碰的方法其實也是出自于UIResponder類,而UIView都是繼承自UIResponder
事件的響應流程:
1、首先已確定響應視圖
2、判斷響應視圖是否能響應事件,如果能則事件在響應鏈中終止傳遞。如果不能則將事件傳遞給 nextResponder ,也就是通常的 superview 進行事件響應
3、如果事件繼續上報至 UIWindow 并且仍無法響應,則將事件上報給UIAPplication
4、如果事件繼續上報至 UIAPplication 并且仍無法響應,則將事件上報給它的Delegate,但前提是這個 Delegate 不屬于 響應鏈 并且是 UIResponder 的子類
5、如果最終事件依舊未被響應,則會被系統拋棄
note:
也并非所有的nextResponder都是superview,比如UIViewController的根視圖self.view的nextResponder是其所在UIViewController。而如果UIViewController如果是UIWindow的根控制器,那么它的nextResponder就是UIWindow,但如果UIViewController是另外一個 UIViewController present出來的話,那么它的nextResponder就是之前所執行present操作的那個UIViewController