iOS觸摸事件
http://www.itdecent.cn/p/c294d1bd963d
一.事件的生命周期

系統(tǒng)響應階段
1.手指觸摸屏幕,生成事件由IOKit處理
2.IOKit將事件觸摸封裝為IOHIDEvent事件,通過mach port傳遞給springboard應用(iphone桌面系統(tǒng))
3.SpringBoard進程接收到觸摸事件,觸發(fā)主線程的source1事件源的回調
根據桌面狀態(tài),判斷誰處理觸摸事件
app未打開,SpringBoard處理
app在后臺,通過IPC(進程間通訊)傳遞給app進程
app響應階段
1.app的mach port 接收到SpringBoard進程傳遞的觸摸事件,主線程runloop被喚醒,觸發(fā)source1回調
2.source1回調觸發(fā)source0回調,將IOHIDEvent轉換為UIEvent,app正式開始對觸摸事件的響應
3.source0回調將觸摸事件添加到UIApplication的事件隊列里(先進先出),開始處理的時候,查找最佳響應者,過程稱為hit-testing
4.找到最佳響應者后(響應鏈確認),事件在響應鏈中傳遞,事件能被響應者/手勢識別器/target-action捕捉并消耗
5.觸摸事件要么響應后釋放,要么被丟棄,runloop若沒有其他事件處理,休眠狀態(tài)
二.觸摸,事件,響應者
觸摸UITouch
@property(nonatomic,readonly) NSTimeInterval timestamp;
@property(nonatomic,readonly) UITouchPhase phase;
@property(nonatomic,readonly) NSUInteger tapCount; // touch down within a certain point within a certain amount of time
@property(nonatomic,readonly) UITouchType type NS_AVAILABLE_IOS(9_0);
@property(nullable,nonatomic,readonly,strong) UIWindow *window;
@property(nullable,nonatomic,readonly,strong) UIView *view;
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);
事件UIEvent
@property(nonatomic,readonly) UIEventType type NS_AVAILABLE_IOS(3_0);
@property(nonatomic,readonly) UIEventSubtype subtype NS_AVAILABLE_IOS(3_0);
@property(nonatomic,readonly) NSTimeInterval timestamp;
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
響應者UIResponder
所有響應者都為UIResponder對象
UIView/UIViewController/UIApplication/AppDelegate
能響應事件的原因是UIResponder里面的方法
- (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;
三.hit-testing
尋找最佳響應者的過程
UIApplication->UIWindow->子視圖->子視圖->子視圖
具體流程:
1.UIApplication首先將事件傳遞給窗口對象(UIWindow),若存在多個窗口,則優(yōu)先詢問后顯示的窗口。
2.若窗口不能響應事件,則將事件傳遞其他窗口;若窗口能響應事件,則從后往前詢問窗口的子視圖。
3.重復步驟2。即視圖若不能響應,則將事件傳遞給上一個同級子視圖;若能響應,則從后往前詢問當前視圖的子視圖。
4.視圖若沒有能響應的子視圖了,則自身就是最合適的響應者。
Hit-Testing的本質
以下幾種狀態(tài)的視圖無法響應事件:
不允許交互:userInteractionEnabled = NO
隱藏:hidden = YES 如果父視圖隱藏,那么子視圖也會隱藏,隱藏的視圖無法接收事件
透明度:alpha < 0.01 如果設置一個視圖的透明度<0.01,會直接影響子視圖的透明度。alpha:0.0~0.01為透明。
hitTest:withEvent:
每個UIView對象都有一個 hitTest:withEvent: 方法,這個方法是Hit-Testing過程中最核心的存在,其作用是詢問事件在當前視圖中的響應者,同時又是作為事件傳遞的橋梁。
hitTest:withEvent: 方法返回一個UIView對象,作為當前視圖層次中的響應者。默認實現(xiàn)是:
若當前視圖無法響應事件,則返回nil
若當前視圖可以響應事件,但無子視圖可以響應事件,則返回自身作為當前視圖層次中的事件響應者
若當前視圖可以響應事件,同時有子視圖可以響應,則返回子視圖層次中的事件響應者
一開始UIApplication將事件通過調用UIWindow對象的 hitTest:withEvent: 傳遞給UIWindow對象,UIWindow的 hitTest:withEvent: 在執(zhí)行時若判斷本身能響應事件,則調用子視圖的 hitTest:withEvent: 將事件傳遞給子視圖并詢問子視圖上的最佳響應者。最終UIWindow返回一個視圖層次中的響應者視圖給UIApplication,這個視圖就是hit-testing的最佳響應者。
四.事件的響應及在響應鏈中的傳遞
經歷Hit-Testing后,UIApplication已經知道事件的最佳響應者是誰了,接下來要做的事情就是:
將事件傳遞給最佳響應者響應
事件沿著響應鏈傳遞
事件響應的前奏
因為最佳響應者具有最高的事件響應優(yōu)先級,因此UIApplication會先將事件傳遞給它供其響應。首先,UIApplication將事件通過 sendEvent: 傳遞給事件所屬的window,window同樣通過 sendEvent: 再將事件傳遞給hit-tested view,即最佳響應者。過程如下:
UIApplication ——> UIWindow ——> hit-tested view
響應者對于事件的操作方式:
響應者對于事件的攔截以及傳遞都是通過 touchesBegan:withEvent: 方法控制的,該方法的默認實現(xiàn)是將事件沿著默認的響應鏈往下傳遞。
響應者對于接收到的事件有3種操作:
不攔截,默認操作
事件會自動沿著默認的響應鏈往下傳遞
攔截,不再往下分發(fā)事件
重寫 touchesBegan:withEvent: 進行事件處理,不調用父類的 touchesBegan:withEvent:
攔截,繼續(xù)往下分發(fā)事件
重寫 touchesBegan:withEvent: 進行事件處理,同時調用父類的 touchesBegan:withEvent: 將事件往下傳遞
參考
http://southpeak.github.io/2015/12/13/cocoa-uikit-uicontrol/
https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events?preferredLanguage=occ#see-also
http://www.itdecent.cn/p/2e074db792ba