iOS事件攔截和事件轉發
響應鏈流程
基本流程
大家都知道 iOS 的響應鏈是 UIApplication 收到使用者觸控螢幕的事件以後通過逐層尋找最後得到使用者觸控的 View 也就是第一響應者,然後呼叫 View 的 touchesBegan:withEvent:
方法處理事件任務的流程.大概流程是這樣的:
圖片很清晰的說明了查詢流程 AppDelegate 收到事件逐層查詢.最終找到 UIButton 這個響應者 然後呼叫 UIButton 的touchesBegan:withEvent:
方法處理事件.
如何查詢第一響應者
查詢第一響應者主要涉及以下兩個方法
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
pointInside:
通過 point 引數確定觸碰點是否在當前 View 的響應範圍內 是則返回YES 否則返回 NO 實現方法大概是這個樣子的
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
return CGRectContainsPoint(self.bounds, point);
}
hitTest方法:
- 它首先會通過呼叫自身的 pointInside 方法判斷使用者觸控的點是否在當前物件的響應範圍內,如果 pointInside 方法返回 NO hitTest方法直接返回 nil
- 如果 pointInside 方法返回 YES hitTest方法接著會判斷自身是否有子檢視.如果有則呼叫頂層子檢視的 hitTest 方法 直到有子檢視返回 View
- 如果所有子檢視都返回 nil hitTest 方法返回自身.
hitTest方法的內部實現虛擬碼
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 判斷觸控位置是否在當前檢視內
if ([self pointInside:point withEvent:event]) {
NSArray<UIView *> * superViews = self.subviews;
// 倒序 從最上面的一個檢視開始查詢
for (NSUInteger i = superViews.count; i > 0; i--) {
UIView * subview = superViews[i - 1];
// 轉換座標系 使座標基於子檢視
CGPoint newPoint = [self convertPoint:point toView:subview];
// 得到子檢視 hitTest 方法返回的值
UIView * view = [subview hitTest:newPoint withEvent:event];
// 如果子檢視返回一個view 就直接返回 不在繼續遍歷
if (view) {
return view;
}
}
// 所有子檢視都沒有返回 則返回自身
return self;
}
return nil;
}
事件傳遞
找到第一響應者 application 便會根據 event 呼叫第一響應者響應的
touch 方法:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
第一響應者在這幾個方法中處理響應的事件,處理完成後根據需要呼叫 nextResponder 的 touch 方法,通常 nextResponder 就是第一響應者的 superView 文章的第一張圖倒著看就是nextResponder 的順序
事件攔截
通常第一響應者都是響應鏈中最末端的響應者,事件攔截就是在響應鏈中截獲事件,停止下發.將事件交由中間的某個響應者執行.比如這樣:
通常點選紅色 view 事件將交由 紅色 view 處理.如果想讓粉色 View 或者綠色 view 處理事件應該怎麼辦?
有兩種辦法
- 在紅色 view 的的 touch 方法中呼叫父類或者 nextResponder 的
touch 方法 - 在需要攔截的 view 中重寫 hitTest 方法改變第一響應者
首先來看第一種
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 將事件傳遞給下一響應者
[self.nextResponder touchesBegan:touches withEvent:event];
// 呼叫父類的touch方法 和上面的方法效果一樣 這兩句只需要其中一句
[super touchesBegan:touches withEvent:event];
}
這種方法有兩個問題,你需要重寫所有的 touch 方法並且還要重寫要攔截事件的 view 與頂級 view 之間的所有 view 的 touch 方法
第二種方法
重寫攔截事件的 view 的 hitTest 方法 比如要讓綠色的 view 處理事件 就重寫綠色 view 的 hitTest 方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 如果在當前 view 中 直接返回 self 這樣自身就成為了第一響應者 subViews 不再能夠接受到響應事件
if ([self pointInside:point withEvent:event]) {
return self;
}
return nil;
}
這種方法比較簡單粗暴.實現後 所有 subview 將不再能夠接受任何事件 具體使用那種方式看需求.當然還可以通過 event 或者 point 有針對性的攔截
事件轉發
有時候還需要將事件轉發出去.讓本來不能響應事件的 view 響應事件,最常用的場景就是讓子檢視超出父檢視的部分也能響應事件,比如要實現這樣的 tabbar
橙色按鈕有兩個區域 a 區超出父檢視 b 區沒有超出父檢視,如果不作處理,那麼點選 a 區是無法響應事件的,因為 a 區域的座標不在父檢視的範圍內,當執行到父檢視的 pointInside 的時候就會返回 NO
想要讓 a 區響應事件 就需要重寫父檢視的 pointInside 或 hitTest 方法讓 pointInside 返回 YES 或 讓hitTest 直接返回橙色檢視
重寫hitTest
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 觸控點在檢視範圍內 則交由父類處理
if ([self pointInside:point withEvent:event]) {
return [super hitTest:point withEvent:event];
}
// 如果觸控點不在範圍內 而在子檢視範圍內依舊返回子檢視
NSArray<UIView *> * superViews = self.subviews;
// 倒序 從最上面的一個檢視開始查詢
for (NSUInteger i = superViews.count; i > 0; i--) {
UIView * subview = superViews[i - 1];
// 轉換座標系 使座標基於子檢視
CGPoint newPoint = [self convertPoint:point toView:subview];
// 得到子檢視 hitTest 方法返回的值
UIView * view = [subview hitTest:newPoint withEvent:event];
// 如果子檢視返回一個view 就直接返回 不在繼續遍歷
if (view) {
return view;
}
}
return nil;
}
重寫 pointInside 方法原理相同 重點注意轉換座標系 就算他們不是一條響應鏈上 也可以通過重寫 hitTest 方法轉發事件.原理相同的東西就不再寫了
擴充套件
關於手勢的處理邏輯和這個相同.但是手勢的優先順序更高.如果父檢視有手勢.預設優先處理手勢事件 可以修改手勢的屬性cancelsTouchesInView
為 NO 來同時處理手勢和普通觸控事件
相關文章
- 2.攔截WebView事件WebView事件
- 講講Android事件攔截機制Android事件
- XSS 前端防火牆(1):內聯事件攔截前端防火牆事件
- 快速理解android事件傳遞攔截機制概念Android事件
- axios攔截器iOS
- axios 攔截器iOS
- IOS 手勢攔截iOS
- vue中用axios攔截器攔截請求和響應VueiOS
- 前端架構之vue+axios 前端實現登入攔截(路由攔截、http攔截)前端架構VueiOS路由HTTP
- iOS 開發中使用 NSURLProtocol 攔截 HTTP 請求iOSProtocolHTTP
- Vue事件獲取觸發事件物件和繫結事件物件Vue事件物件
- axios原始碼分析——攔截器iOS原始碼
- 記錄下:iOS事件的事件的傳遞和響應iOS事件
- JS事件(事件冒泡和事件捕獲)JS事件
- touch事件和click事件多次觸發的問題事件
- React 事件和 Dom 事件React事件
- onscroll 事件和onScrollCapture事件事件APT
- axios 攔截器 的使用方法iOS
- iOS 簡訊攔截 Message Filter ExtensioniOSFilter
- 攔截器,攔截器棧總結
- 配置filter攔截forward之類的內部轉發FilterForward
- 事件分發和處理事件
- axios的全域性攔截之axios.interceptorsiOS
- 事件和事件監聽器事件
- 事件模型和事件委託事件模型
- 事件冒泡 和 事件捕獲事件
- 攔截Internet垃圾郵件 (轉)
- iOS事件分發機制與實踐iOS事件
- iOS事件分發機制(二)The Responder ChainiOS事件AI
- [DOM Event Learning] Section 4 事件分發和DOM事件流事件
- iOS開發學習之觸控事件和手勢識別iOS事件
- JavaScript事件冒泡、事件捕獲和阻止預設事件JavaScript事件
- 事件系統-z 事件發現事件
- JS事件流和事件委託JS事件
- input事件和change事件區別事件
- 模擬tap事件和longTap事件事件
- 事件物件(轉)事件物件
- Asp.Netcore使用Filter來實現介面的全域性異常攔截,以及前置攔截和後置攔截ASP.NETNetCoreFilter