長治做網(wǎng)站公司網(wǎng)絡(luò)服務(wù)公司
響應(yīng)者鏈
響應(yīng)者鏈?zhǔn)怯梢幌盗墟溄釉谝黄鸬捻憫?yīng)者(UIResponser
之類:UIApplication
,UIViewController
,UIView
)注組成的。一般情況下,一條響應(yīng)鏈開始于第一響應(yīng)者,結(jié)束于application
對(duì)象。如果一個(gè)響應(yīng)者不能處理事件,會(huì)將事件沿著響應(yīng)者鏈傳到下一個(gè)響應(yīng)者
響應(yīng)者對(duì)象
在響應(yīng)者鏈中,每個(gè)響應(yīng)者對(duì)象都可以處理事件,也可以選擇將事件傳遞給下一個(gè)響應(yīng)者對(duì)象進(jìn)行處理,或者直接丟棄事件。響應(yīng)者鏈中的每個(gè)響應(yīng)者對(duì)象都可以重寫幾個(gè)方法來處理事件,這些方法包括touchesBegan:withEvent:
、touchesMoved:withEvent:
、touchesEnded:withEvent:
等等。
響應(yīng)者對(duì)象都實(shí)現(xiàn)了UIResponder
協(xié)議,這里的實(shí)現(xiàn)UIResponder協(xié)議指UIApplication、UIViewController、UIView 都繼承于 UIResponder。
常見的響應(yīng)者對(duì)象
- UIView:是iOS中最基本的用戶界面元素,可以接收用戶的觸摸事件并進(jìn)行相關(guān)的處理。
- UIViewController:作為MVC模式中的控制器,可以響應(yīng)用戶的觸摸事件,同時(shí)還可以管理一個(gè)或多個(gè)視圖控制器。
- UIWindow:是整個(gè)應(yīng)用程序的窗口,它包含了一個(gè)或多個(gè)視圖,并且是接收和處理觸摸事件的最高層響應(yīng)者對(duì)象。
- UIGestureRecognizer:是iOS中專門用來處理手勢(shì)事件的響應(yīng)者對(duì)象,包括UITapGestureRecognizer、UIPanGestureRecognizer、UILongPressGestureRecognizer等等。
- UIScrollView:是一個(gè)可以滾動(dòng)的視圖控件,它可以接收用戶的觸摸事件,并在觸摸拖動(dòng)時(shí)進(jìn)行滾動(dòng)。
- UITableView:是iOS中常用的列表視圖控件,它可以顯示大量的數(shù)據(jù),并且可以處理用戶的滑動(dòng)、點(diǎn)擊等事件
響應(yīng)者事件
iOS 中的事件可分為:觸摸事件(multitouch events)、加速計(jì)事件(accelerometer events):包括搖晃、傾斜、加速等設(shè)備運(yùn)動(dòng)、遠(yuǎn)程控制事件(remote control events):可以來自于耳機(jī)、鎖屏界面和控制中心等,例如暫停音樂、切換歌曲等。
基本元素的了解
系統(tǒng)是怎么響應(yīng)用戶的觸屏事件,這里有與用戶事件相關(guān)的類,它們分別是UITouch
, UIEvent
和UIResponder
UIResponder(事件響應(yīng)者)
UIResponder是iOS中的一個(gè)基類,定義了一些接口,用于處理觸摸事件和鍵盤事件。所有能夠接受并處理事件的對(duì)象都繼承于UIResponder
- UIResponder內(nèi)部提供了以下方法來處理事件
// 一根或者多根手指開始觸摸view,系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手指在view上移動(dòng),系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法(隨著手指的移動(dòng),會(huì)持續(xù)調(diào)用該方法)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手指離開view,系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 觸摸結(jié)束前,某個(gè)系統(tǒng)事件(例如電話呼入)會(huì)打斷觸摸過程,系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法[可選]
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
通過重寫UIresponder中定義的方法,開發(fā)者可以自己類中處理用戶事件,并做出相應(yīng)的事件
UITouch(觸摸)
- UITouch是觸摸對(duì)象,一個(gè)手指一次觸摸屏幕就會(huì)生成一個(gè)UITouch對(duì)象
- 若兩個(gè)手指先后觸摸同一個(gè)位置,第一次觸摸時(shí)生成一個(gè)UITouch對(duì)象,第二次觸摸更新UITouch對(duì)象的tapCount屬性值由1變成2;如果兩個(gè)手指一前一后觸摸的位置不同,將會(huì)生成兩個(gè)UITouch對(duì)象,兩者沒有聯(lián)系。
- 每個(gè)UITouch對(duì)象會(huì)記錄觸摸的一些記錄,包括觸摸時(shí)間、位置、階段、所處的視圖和窗口等信息。 當(dāng)手指移動(dòng)時(shí),系統(tǒng)會(huì)更新同一個(gè)UITouch對(duì)象,使之能夠保存該手指的觸摸位置;當(dāng)手指離開屏幕上時(shí),系統(tǒng)會(huì)銷毀響應(yīng)的UITouch對(duì)象
- UITouch對(duì)象會(huì)在觸摸事件的過程中不斷更新,直到觸摸事件結(jié)束。在觸摸事件的過程中,系統(tǒng)會(huì)不斷向響應(yīng)鏈的響應(yīng)者發(fā)生事件,并將觸摸事件封裝成UIEvent對(duì)象進(jìn)行傳遞。當(dāng)觸摸事件結(jié)束時(shí),系統(tǒng)就會(huì)銷毀相應(yīng)的UITouch對(duì)象
UITouch的屬性
// 記錄了觸摸事件產(chǎn)生或變化時(shí)的時(shí)間,單位是秒 The relative time at which the acceleration event occurred(read-only)
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 當(dāng)前觸摸事件所處的狀態(tài)
@property(nonatomic,readonly) UITouchPhase phase;
// touch down within a certain point within a certain amount of timen 短時(shí)間內(nèi)點(diǎn)按屏幕的次數(shù),可以根據(jù)tapCount判斷單擊、雙擊或更多的點(diǎn)擊
@property(nonatomic,readonly) NSUInteger tapCount;
@property(nonatomic,readonly) UITouchType type NS_AVAILABLE_IOS(9_0);
// 觸摸產(chǎn)生時(shí)所處的窗口
@property(nullable,nonatomic,readonly, strong) UIWindow *window;
// 觸摸產(chǎn)生時(shí)所處的視圖
@property(nullable,nonatomic,readonly, strong) UIView *view;
// The gesture-recognizer objects currently attached to the view.
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers
UITouch方法
/*返回值表示觸摸在view上的位置
這里返回的位置是針對(duì)view的坐標(biāo)系的(以view的左上角為原點(diǎn)(0, 0))
調(diào)用時(shí)傳入的view參數(shù)為nil的話,返回的是觸摸點(diǎn)在UIWindow的位置*/
- (CGPoint)locationInView:(nullable UIView *)view;
// 該方法記錄了前一個(gè)觸摸點(diǎn)的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;// Use these methods to gain additional precision that may be available from touches.
// Do not use precise locations for hit testing. A touch may hit test inside a view, yet have a precise location that lies just outside.//獲取指定視圖上的精確觸摸位置,該方法會(huì)考慮到多點(diǎn)觸控時(shí)不同觸點(diǎn)之間的偏移。
- (CGPoint)preciseLocationInView:(nullable UIView *)view API_AVAILABLE(ios(9.1));
// 獲取指定視圖上上一次觸摸的精確位置。
- (CGPoint)precisePreviousLocationInView:(nullable UIView *)view API_AVAILABLE(ios(9.1));
UIEvent(事件)
UIEvent
是 iOS 中用于表示觸摸事件的類,一個(gè) UIEvent 對(duì)象包含了所有與觸摸事件相關(guān)的信息,比如觸摸的位置、時(shí)間、階段,以及多點(diǎn)觸控時(shí)不同觸點(diǎn)之間的狀態(tài)等等。UIEvent 對(duì)象是由系統(tǒng)自動(dòng)創(chuàng)建和管理的,通常情況下不需要手動(dòng)創(chuàng)建。每產(chǎn)生一個(gè)事件,就會(huì)產(chǎn)生一個(gè) UIEvent 對(duì)象,UIEvent 稱為事件對(duì)象。
事件類型屬性
//事件類型,枚舉值包括觸摸、運(yùn)動(dòng)、遙控等。
@property(nonatomic,readonly) UIEventType type NS_AVAILABLE_IOS(3_0);// 事件子類型,對(duì)于觸摸事件,其子類型包括touch down、touch move、touch up等。
@property(nonatomic,readonly) UIEventSubtype subtype NS_AVAILABLE_IOS(3_0);
產(chǎn)生時(shí)間的事件屬性
事件發(fā)生的時(shí)間戳,單位為秒。
@property(nonatomic,readonly) NSTimeInterval timestamp;
事件的傳遞和響應(yīng)
- 步驟一:尋找目標(biāo),在iOS的視圖層次結(jié)構(gòu)中找到事件的最終接受者
- 步驟二:事件響應(yīng)·,基于iOS響應(yīng)者鏈處理觸摸事件
事件的傳遞:尋找事件的第一響應(yīng)者(Hit_Testing)
當(dāng)一個(gè)事件發(fā)生時(shí),事件會(huì)從父控件傳給子控件
也就是說由
- 硬件 -> 系統(tǒng) ->
UIApplication
->UIWindow
->SuperView
->SubView
以上就是事件的傳遞,也就是尋找第一響應(yīng)者的過程。
符合第一響應(yīng)者的條件包括:
- touch事件的位置在響應(yīng)者區(qū)域內(nèi) pointInside:withEvent: == YES
- 響應(yīng)者 self.hidden != NO
- 響應(yīng)者 self.alpha > 0.01
- 響應(yīng)者 self.userInteractionEnabled = YES
- 遍歷 subview 時(shí),是從上往下順序遍歷的,即 view.subviews 的 + + + lastObject 到 firstObject 的順序,找到合適的響應(yīng)者view,即停止遍歷.
第一響應(yīng)者對(duì)于接收到的事件的三種操作:
- 不攔截,默認(rèn)操作。事件會(huì)自動(dòng)沿著默認(rèn)的響應(yīng)者鏈往下傳遞
- 攔截,不再往下分發(fā)事件。重寫
touchesBegan:withEvent:
進(jìn)行事件處理,不調(diào)用父類的touchesBegan:withEvent:
- 攔截,繼續(xù)往下分發(fā)事件。重寫
touchesBegan:withEvent:
進(jìn)行事件處理,同時(shí)調(diào)用父類的touchesBegan:withEvent:
將事件往下傳遞
事件的響應(yīng):一旦事件的第一響應(yīng)者確定了,這個(gè)事件的響應(yīng)鏈就確定了
下圖是官網(wǎng)對(duì)于響應(yīng)者鏈的實(shí)例展示
每個(gè)響應(yīng)者對(duì)象(UIResponder)對(duì)象都有一個(gè)nextResponder
方法,用于獲取響應(yīng)者鏈中當(dāng)前對(duì)象的下一個(gè)響應(yīng)者。
- 圖中虛線箭頭是指若該
UIView
是作為UIViewController
根視圖存在的,則其nextResponder
為UIViewController
對(duì)象; - 若是直接add在
UIWindow
上的,則其nextResponder
為UIWindow
對(duì)象。
若觸摸發(fā)生在UITextField
上,則事件的傳遞順序是:
UITextField
——>UIView
——>UIView
——>UIViewController
——>UIWindow
——>UIApplication
——>UIApplicationDelegation
雖然兩個(gè)傳遞過程都設(shè)計(jì)到父子控件的傳遞,但它們的傳遞順序和目的不同,觸摸事件的傳遞過程主要是為了找到最合適的空間來處理事件,而響應(yīng)者鏈傳遞過程是為了讓控件的響應(yīng)者對(duì)象能夠逐級(jí)處理事件。
事件的生命周期
手指觸摸屏幕的一刻,系統(tǒng)會(huì)生成一個(gè)觸摸事件。經(jīng)過IPC進(jìn)程間通信,事件最終被傳遞給了合適的應(yīng)用。
(一)系統(tǒng)響應(yīng)階段
- 屏幕感應(yīng)到觸碰后,將事件交給IOKit處理,IOKit是監(jiān)測硬件的框架。IOKit將觸摸事件封裝成一個(gè)IOHIDEvent對(duì)象,并通過mach port傳遞給SpringBoard進(jìn)程。
mach port是進(jìn)程端口,各個(gè)進(jìn)程之間通過它進(jìn)行通信;
SpringBoard.app是一個(gè)系統(tǒng)進(jìn)程,可以理解為桌面系統(tǒng),可以統(tǒng)一管理和分發(fā)系統(tǒng)接收到的觸摸事件;
- SpringBoard.app進(jìn)程收到觸摸事件,觸發(fā)主線程RunLoop的source1事件源的回調(diào)。SpringBoard.app會(huì)根據(jù)當(dāng)前桌面的狀態(tài),判斷應(yīng)該由誰響應(yīng)此次觸摸事件。如果沒有APP在運(yùn)行,則由SpringBoard處理該事件;如果有APP在運(yùn)行,則由APP處理該事件;
(二)APP響應(yīng)階段
- APP進(jìn)程的mach port接收到SpringBoard進(jìn)程傳遞來的觸摸事件,主線程的RunLoop被喚醒,觸發(fā)source1回調(diào);
- source1回調(diào)觸發(fā)了一個(gè)source0回調(diào),將接收到的IOHIDEvent對(duì)象封裝成UIEvent對(duì)象;
- source0回調(diào)內(nèi)部將觸摸事件添加到UIApplication對(duì)象的事件隊(duì)列中。事件出隊(duì)列后,UIApplication開始尋找一個(gè)最佳響應(yīng)者的過程,這個(gè)過程又稱為hit-testing,具體細(xì)節(jié)在第二個(gè)主題尋找最佳響應(yīng)者中闡述;
- 找到最佳響應(yīng)者后,事件就在響應(yīng)鏈中傳遞和響應(yīng),這里涉及到“事件的響應(yīng)和響應(yīng)鏈中的傳遞”;
- 經(jīng)過上述流程,觸摸事件要么被某個(gè)響應(yīng)對(duì)象捕獲后釋放,要么沒有找到能響應(yīng)的對(duì)象被釋放;
總結(jié):觸摸事件從觸屏產(chǎn)生后,有IOKit
將觸摸事件傳遞給SpringBoard
進(jìn)程,再由SpingBoard
分發(fā)給當(dāng)前前臺(tái)APP
處理,觸發(fā)事件響應(yīng)者鏈?zhǔn)录?/p>
完整的觸摸過程
一個(gè)完整的觸摸事件流程通常包括以下幾個(gè)步驟:
- 手指觸摸到屏幕,系統(tǒng)會(huì)創(chuàng)建一個(gè)與手指相關(guān)聯(lián)的
UITouch
對(duì)象,并將其加入到系統(tǒng)中的事件隊(duì)列中。 - 系統(tǒng)會(huì)將該事件發(fā)送給當(dāng)前
UIWindow
對(duì)象,即調(diào)用UIWindow
對(duì)象的touchesBegan(_:with:)
方法,并將該事件傳遞給子視圖。 - 從根視圖開始,系統(tǒng)會(huì)通過遞歸調(diào)用
hitTest(_:with:)
方法,尋找響應(yīng)該事件的視圖。在每個(gè)視圖中,系統(tǒng)都會(huì)調(diào)用point(inside:with:)
方法,判斷該視圖是否包含該事件的觸摸點(diǎn)。 - 一旦找到了響應(yīng)該事件的視圖,系統(tǒng)會(huì)將該事件發(fā)送給該視圖,即調(diào)用該視圖的
touchesBegan(:with:)
方法。 - 在該視圖的
touchesBegan(:with:)
方法中,開發(fā)者可以對(duì)該事件做出相應(yīng)的處理,比如更改視圖的狀態(tài)、更新視圖的內(nèi)容等。 - 如果該事件需要傳遞給其它視圖進(jìn)行處理,開發(fā)者可以手動(dòng)調(diào)用
next
方法,將該事件傳遞給下一個(gè)響應(yīng)者。 - 當(dāng)手指離開屏幕時(shí),系統(tǒng)會(huì)將一個(gè)
touch
對(duì)象的phase
屬性設(shè)置為.ended
,并將該touch
對(duì)象從事件隊(duì)列中移除。 - 當(dāng)前的
UIWindow
對(duì)象會(huì)將該事件發(fā)送給響應(yīng)者鏈中的下一個(gè)響應(yīng)者。如果沒有下一個(gè)響應(yīng)者,則該事件的響應(yīng)過程結(jié)束。
總結(jié):
- 當(dāng)觸摸事件發(fā)生后,系統(tǒng)會(huì)自動(dòng)生成一個(gè)
UIEvent
對(duì)象,記錄事件發(fā)生的事件和類型 - 然后系統(tǒng)會(huì)把
UIEvent
事件加入到一個(gè)由UIApplocation
管理的事件隊(duì)列中 - 然后
UIApplication
會(huì)講事件分發(fā)給UIWindow
,主窗口會(huì)在視圖層次結(jié)構(gòu)中找到一個(gè)合適的響應(yīng)者對(duì)象來處理觸摸事件。 - 不斷遞歸調(diào)用
hitTest
方法來找到第一響應(yīng)者 - 如果第一響應(yīng)者無法響應(yīng)事件,那么會(huì)按照響應(yīng)者鏈往上傳遞,也就是傳遞給自己的父視圖
- 一直傳遞直到
UIApplication
,如果都無法響應(yīng),事件就會(huì)被丟棄