iOS觸摸事件
http://www.reibang.com/p/c294d1bd963d
一.事件的生命周期
系統(tǒng)響應階段
1.手指觸摸屏幕滚停,生成事件由IOKit處理
2.IOKit將事件觸摸封裝為IOHIDEvent事件,通過mach port傳遞給springboard應用(iphone桌面系統(tǒng))
3.SpringBoard進程接收到觸摸事件,觸發(fā)主線程的source1事件源的回調(diào)
根據(jù)桌面狀態(tài)壁肋,判斷誰處理觸摸事件
app未打開硅蹦,SpringBoard處理
app在后臺偏友,通過IPC(進程間通訊)傳遞給app進程
app響應階段
1.app的mach port 接收到SpringBoard進程傳遞的觸摸事件裤翩,主線程runloop被喚醒芥炭,觸發(fā)source1回調(diào)
2.source1回調(diào)觸發(fā)source0回調(diào)享怀,將IOHIDEvent轉(zhuǎn)換為UIEvent羽峰,app正式開始對觸摸事件的響應
3.source0回調(diào)將觸摸事件添加到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的本質(zhì)
以下幾種狀態(tài)的視圖無法響應事件:
不允許交互:userInteractionEnabled = NO
隱藏:hidden = YES 如果父視圖隱藏息楔,那么子視圖也會隱藏,隱藏的視圖無法接收事件
透明度:alpha < 0.01 如果設(shè)置一個視圖的透明度<0.01扒披,會直接影響子視圖的透明度值依。alpha:0.0~0.01為透明。
hitTest:withEvent:
每個UIView對象都有一個 hitTest:withEvent: 方法碟案,這個方法是Hit-Testing過程中最核心的存在愿险,其作用是詢問事件在當前視圖中的響應者,同時又是作為事件傳遞的橋梁价说。
hitTest:withEvent: 方法返回一個UIView對象辆亏,作為當前視圖層次中的響應者。默認實現(xiàn)是:
若當前視圖無法響應事件鳖目,則返回nil
若當前視圖可以響應事件扮叨,但無子視圖可以響應事件,則返回自身作為當前視圖層次中的事件響應者
若當前視圖可以響應事件领迈,同時有子視圖可以響應彻磁,則返回子視圖層次中的事件響應者
一開始UIApplication將事件通過調(diào)用UIWindow對象的 hitTest:withEvent: 傳遞給UIWindow對象碍沐,UIWindow的 hitTest:withEvent: 在執(zhí)行時若判斷本身能響應事件,則調(diào)用子視圖的 hitTest:withEvent: 將事件傳遞給子視圖并詢問子視圖上的最佳響應者衷蜓。最終UIWindow返回一個視圖層次中的響應者視圖給UIApplication累提,這個視圖就是hit-testing的最佳響應者。
四.事件的響應及在響應鏈中的傳遞
經(jīng)歷Hit-Testing后恍箭,UIApplication已經(jīng)知道事件的最佳響應者是誰了刻恭,接下來要做的事情就是:
將事件傳遞給最佳響應者響應
事件沿著響應鏈傳遞
事件響應的前奏
因為最佳響應者具有最高的事件響應優(yōu)先級,因此UIApplication會先將事件傳遞給它供其響應扯夭。首先鳍贾,UIApplication將事件通過 sendEvent: 傳遞給事件所屬的window,window同樣通過 sendEvent: 再將事件傳遞給hit-tested view交洗,即最佳響應者骑科。過程如下:
UIApplication ——> UIWindow ——> hit-tested view
響應者對于事件的操作方式:
響應者對于事件的攔截以及傳遞都是通過 touchesBegan:withEvent: 方法控制的,該方法的默認實現(xiàn)是將事件沿著默認的響應鏈往下傳遞构拳。
響應者對于接收到的事件有3種操作:
不攔截咆爽,默認操作
事件會自動沿著默認的響應鏈往下傳遞
攔截,不再往下分發(fā)事件
重寫 touchesBegan:withEvent: 進行事件處理置森,不調(diào)用父類的 touchesBegan:withEvent:
攔截斗埂,繼續(xù)往下分發(fā)事件
重寫 touchesBegan:withEvent: 進行事件處理,同時調(diào)用父類的 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.reibang.com/p/2e074db792ba