iOS
中 View
的事件究竟是怎麼傳遞
和響應
的?父View
關閉了事件響應時,子View
就沒法響應事件? 底層原理?Button
的點擊範圍 ?父View
和 子View
同時響應同一事件?默認狀況下只會響應 子View
的事件回調。子View
關閉了事件,但其 父View
開啓事件的狀況下,點擊 子View
時,父View
能夠正常響應事件?
iOS
的事件能夠分爲三種html
Touch Events(觸摸事件)
Motion Events(運動事件,好比重力感應和搖一搖等)
Remote Events(遠程事件,好比用耳機上得按鍵來控制手機)
下面主要講解
Touch Events(觸摸事件)
Touch Events
事件的整個過程能夠分爲傳遞
和響應
2 個階段,ios
- 傳遞: 是當咱們觸摸屏幕時,爲咱們找出最適合的
View
,- 響應: 當咱們找出最適合的
View
後,此時只是找到了最合適的View
,但未必 此View
能夠響應此事件,因此須要繼續找出能響應此事件的View
。
每當手指接觸屏幕,操做系統會把事件傳遞給當前的
App
, 在UIApplication
接收到手指的事件以後,就會去調用`UIWindow的hitTest:withEvent:,看看當前點擊的點是否是在window內,若是是則繼續依次調用其 subView的hitTest:withEvent:方法,直到找到最後須要的view。調用結束而且hit-test view肯定以後,即可以肯定最合適的 View。git
遞歸是向界面的根節點UIWindow發送hitTest:withEvent:消息開始的,從這個消息返回的是一個UIView,也就是手指當前位置最前面的那個 hittest view。 當向UIWindow發送hitTest:withEvent:消息時,hitTest:withEvent:裏面所作的事,就是判斷當前的點擊位置是否在window裏面,若是在則遍歷window的subview而後依次對subview發送hitTest:withEvent:消息(注意這裏給subview發送消息是根據當前subview的index順序,index越大就越先被訪問)。若是當前的point沒有在view上面,那麼這個view的subview也就不會被遍歷了。當事件遍歷到了view B.1,發現point在view B.1裏面,而且view B.1沒有subview,那麼他就是咱們要找的hittest view了,找到以後就會一路返回直到根節點,而view B以後的view A也不會被遍歷了。github
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法的內部實現- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self _isAnimatedUserInteractionEnabled]) {
return nil;
} else {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
if (hitView) {
return hitView;
}
}
return self;
}
}
複製代碼
上面的代碼來自這裏bash
當咱們知道最合適的 View 後,事件會 由上向下【子view -> 父view,控制器view -> 控制器】來找出合適響應事件的 View,來響應相關的事件。若是當前的 View 有添加手勢,那麼直接響應相應的事件,不會繼續向下尋找了,若是沒有手勢事件,那麼會看其是否實現了以下的方法:app
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
複製代碼
若是有實現那麼就由此 View 響應,若是沒有實現,那麼就會傳遞給他的下一個響應者【子view -> 父view,控制器view -> 控制器】, 這裏咱們能夠作一個簡單的驗證,在默認狀況下 UIView 是不響應事件的,UIControl 就算沒有添加手勢同樣的會由他來響應, 這裏可使用 runtime查看 UIView 和 UIControl 的方法列表, 或 查看 UIKit 源碼 可知, UIView 沒有實現如上的 touchesBegan
方法,而 UIControl
是實現瞭如上的相關方法,因此驗證了剛纔的 UIView 不響應,和 UIControl 的響應。一旦找到最合適響應的View就結束, 在執行響應的綁定的事件,若是沒有就拋棄此事件。iview
個人驗證ide
@implementation BMSonView
- (NSArray<UIGestureRecognizer *> *)gestureRecognizers {
NSLog(@"%@", self);
return @[];
}
複製代碼
手勢返回 @[],此時點擊 B 只會觸發 A 的事件,由此能夠說明在判斷 view 是否能夠處理事件實現是判斷 gestureRecognizers 便是否添加了手勢,上面提到了還有判斷以下的方法是否實現了,默認狀況下 UIView 是沒有實現以下的方法的,使用在沒有添加手勢時他不響應事件。ui
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
複製代碼
若是咱們手動實現瞭如上的方法時,就算沒有給 B 添加手勢,點擊 B 時, 事件不會響應 A 的方法,會到上面的方法中。從 UIControl 的源碼即可清除看到。spa
因此我的理解:
事件在傳遞時和上面的 hit 方法有關,一層層向上傳遞,【窗口---> view】由其相應的 view 中具體的實現來肯定誰纔是是最合適響應的view
在響應時,又上向下找出第一個能處理的view來處理事件,[view ---> 窗口],在尋找剛過程當中 會判斷是否增長了手勢 和是否實現瞭如上的 觸摸方法。
至於 UIControl Button 的特殊事件相應,我的認爲是在其m文件中實現了上面的4個方法,在這4個方法中作了相關的處理,這裏能夠從 UIControl 代碼中在知道一些內容。
因此若是想本身實現 UIControl Button ,首先要想辦法處理好上面的4個方法。
圖以下
如上所描。
由於在事件傳遞的時,先到父view,當父view沒法響應事,直接就跳過了遍歷其子view,故只要父類關閉了事件,子 view 就已經沒有機會響應事件了。
擴大點擊範圍,無非就是想原本沒有點擊 btn 但想讓 btn 響應事件,那麼能夠在 hitTest 方法中作適當的操做,當知足xxx條件時,強行返回 btn 來達到最佳點擊範圍的效果,相關的實現能夠自行 Google ,有一些較優雅而簡潔的方式。
父View 和 子View同時響應同一事件,默認當點擊子view時,若是ziview能夠處理事件,那麼其餘父view 是不會響應的,可是在 父view 傳到 子view 時咱們在 hitTest 方法中是清楚知道的,使用能夠在這裏作相關的操做便實現了子view 和父view 同時響應事件的效果。
子view關閉了事件,事件的傳遞是 父view 到子view,在 父view時,父view能夠響應,那麼會繼續訪問其 子view是否能夠響應,若是此時子view不能夠響應,那麼他會直接返回 父view,因此 子View 關閉了事件 父View 正常執行事件是必然的。
這個問題能夠見上面的尋找能夠響應的 view 來解決,UIControl 實現瞭如上的 4 大方法,而 UIView 沒有實現。