從用戶觸摸屏幕到app的響應過程。
過程
事件的產生及分發
- 點擊屏幕會產生一個觸摸事件。
- 主線程的runloop會接收到該事件并將其存放到消息隊列中。
- UIApplication 會從消息隊列中取事件并將事件分發給window。
找尋合適的視圖
在尋找合適視圖的過程中,主要會用到UIView的兩個方法:- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
以及- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
。
hitTest方法中會對視圖進行一定的判斷:比如 userInteractionEnabled 、isHidden 、alpha 是否>=0.01,如果視圖可交互,非隱藏,透明度比0.01大,就會調用pointInside方法判定事件的觸發點是否在視圖區域內,否則hitTest會返回nil。
- 事件被分發到window,事件就開始在視圖樹中傳遞,尋找合適的視圖。
- 視圖會調用hitTest方法,在hitTest方法中會調用pointInside方法,若pointInside返回NO,hitTest返回nil。如果pointInside返回YES,則判斷該視圖是否有子視圖。
- 如果沒有子視圖,該視圖的hitTest會返回該視圖。如果有子視圖,會倒序遍歷子視圖,子視圖會調用hitTest。
- 如果所有的子視圖的hitTest都返回nil,則該視圖的hitTest返回該視圖。
- 若第一次有hitTest返回非空的視圖對象。則遞歸返回該視圖,該視圖就是最合適的視圖。
- 如果最終沒有找到合適的視圖,window會將事件返還給UIApplication,系統會忽略掉此事件。
事件的響應
- 找到合適的響應者后,事件的響應鏈已確定。
- 首先判斷響應者是否能響應此事件。如果可以響應,則響應事件。
- 如果不能響應,則會沿著響應鏈來尋找響應者。如果最終沒有找到能響應的響應者,則系統會忽略掉此事件。
響應鏈的構建情況:
- view:如果view是被UIViewController對象管理,view的下一個響應者就是UIViewController;如果不是被UIViewController對象管理的,下一個響應者就是superview。
- UIViewController:同返回它管理的view的superview。
- UIWindow:返回application對象。
- UIApplication:返回給應用委托AppDelegate。
注意點兒
一次觸摸事件,觸發兩次hitTest:
- uiview(geometry) _hitTest:withEvent:windowServerHitTestWindow:
- __updateTouchesWithDigitizerEventAndDeterminelfShouldSend_block_invoke
UIControl 和 UIButton ,會影響事件的響應。
如果能響應事件,則響應事件。若不能響應,會截斷事件的響應。
UIGestureRecognizer 和 UITextField ,會影響事件響應。
若能響應事件,會將觸摸取消。若不能響應,會攔截事件的響應。
UIGestureRecognizer是一個基于NSObject的類,它能影響事件的響應,是因為它會被添加到一個View上。
UITextField ,UIButton 是基于UIControl的類, UIControl 是基于UIview的。
UIApplication 和 UIViewController 是基于 UIResponder的類。
應用
根據事件的傳遞和響應機制,我們可以在事件傳遞和響應過程中做操作,以改變原有的事件響應。
- 事件透傳
- 改變響應區域