這篇文章主要想弄清楚事件(如觸摸屏幕)產生後,系統是如何通知到你的 App,在 App 內部是如何進行傳遞,最終又是如何肯定最終的響應者的。html
這些確定是有規則的,在 App 內部,一個事件會按照一個規則(視圖層級關係)去遍歷尋找這個事件的最佳響應者,可是這個響應者有可能不處理事件,那麼它又須要沿着必定的規則(響應者鏈)去傳遞這個事件,若是最終都無人處理,那麼將這個事件拋棄,也就是不處理。ios
先來看看什麼是事件。git
事件對應的對象爲 UIEvent
,它有一個屬性爲 type
,是 EventType
類型,EventType
是一個枚舉類型:github
public enum EventType : Int {
case touches // 觸摸事件
case motion // 運動事件
case remoteControl // 遠程控制事件
@available(iOS 9.0, *)
case presses // 按壓事件
}
複製代碼
因此 iOS 中的事件有四種:面試
觸摸事件就是咱們的手指或者蘋果的 Pencil(觸筆)在屏幕中所引起的互動,好比輕點、長按、滑動等操做,是咱們最常接觸到的事件類型。觸摸事件對象能夠包含一個或多個觸摸,而且每一個觸摸由 UITouch
對象表示。當觸摸事件發生時,系統會將其沿着線路傳遞,找到適當的響應者並調用適當的方法,例如 touchedBegan:withEvent:
。響應者對象會根據觸摸來肯定適當的方法。算法
觸摸事件分爲如下幾類:bootstrap
UILongPressGestureRecognizer
)UIPanGestureRecognizer
)UIPinchGestureRecognizer
)UIScreenEdgePanGestureRecognizer
)UISwipeGestureRecognizer
)UIRotationGestureRecognizer
)UITapGestureRecognizer
)button
相關觸摸事件對應的對象爲 UITouch
。swift
iPhone 內置陀螺儀、加速器和磁力儀,能夠感知手機的運動狀況。iOS 提供了 Core Motion
框架來處理這些運動事件。根據這些內置硬件,運動事件大體分爲三類:服務器
X-Y-Z
軸的自轉速率、傾斜角度等。經過 Core Motion
提供的一些 API 能夠獲取到這些數據,並進行處理;經過系統能夠經過內置陀螺儀獲取設備的朝向,以此對 App UI 作出調整X-Y-Z
軸速度的改變;Core Motion
提供了高度計(CMAltimeter
)、計步器(CMPedometer
)等對象,來獲取處理這些產生的數據不過官方文檔中指出,這些都是屬於 Core Motion
庫框架,Core Motion
庫中的事件直接由 Core Motion
內部進行處理,不會經過響應者鏈,因此 UIKit
框架能接收的事件暫時只包括搖一搖(EventSubtype.motionShake
)。app
遠程控制事件容許響應者對象從外部附件或耳機接受命令,以便它能夠管理音頻和視頻。目前 iOS 僅提供咱們遠程控制音頻和視頻的權限,即對音頻實現暫停/播放、上一曲/下一曲、快進/快退操做。如下是它能識別的類型:
public enum EventSubtype : Int {
case remoteControlPlay
case remoteControlPause
case remoteControlStop
case remoteControlTogglePlayPause
case remoteControlNextTrack
case remoteControlPreviousTrack
case remoteControlBeginSeekingBackward
case remoteControlEndSeekingBackward
case remoteControlBeginSeekingForward
case remoteControlEndSeekingForward
}
複製代碼
iOS 9.0 以後提供了 3D Touch 事件,經過使用這個功能能夠作以下操做:
咱們通常說的事件傳遞的起點在於 UIApplication
所管理的事件隊列中開始分發的時候,但事件真正的起點在於你手指觸摸到屏幕的那一刻開始(以觸摸事件爲例),那麼在觸摸屏幕到事件隊列開始分發發生了什麼?咱們就以一個觸摸事件來講明這個過程。
IOKit.framework
將事件封裝成一個 IOHIDEvent
對象mach port
(IPC 進程間通訊)轉發到 Springboardmach port
(IPC 進程間通訊)轉發給當前 App 的主線程RunLoop
接收到 Springboard 轉發過來的消息以後,觸發對應的 mach port
的 Source1
回調 __IOHIDEventSystemClientQueueCallback()
Source1
回調內部觸發了 Source0
的回調 __UIApplicationHandleEventQueue()
Source0
回調內部,封裝 IOHIDEvent
爲 UIEvent
Source0
回調內部調用 UIApplication
的 +sendEvent:
方法,將 UIEvent
傳給當前 UIWindow
IOKit.framework
是一個系統框架的集合,用來驅動一些系統事件。IOHIDEvent
中的HID
表明 Human Interface Device,即人機交互驅動
SpringBoard
是一個應用程序,用來管理 iOS 的主屏幕,除此以外像WindowServer(窗口服務器)
、bootstrapping(引導應用程序)
,以及在啓動時候系統的一些初始化設置都是由這個特定的應用程序負責的。它是咱們 iOS 程序中,事件的第一個接收者。它只能接受少數的事件,好比:按鍵(鎖屏/靜音等)、觸摸、加速、接近傳感器等幾種 Event,隨後使用mach port
轉發給須要的 App 進程
UIApplication
管理了一個事件隊列,之因此是隊列而不是棧,是由於隊列的特色是先進先出,先產生的事件先處理。UIApplication
會從事件隊列中取出最前面的事件,並將事件分發下去以便處理,一般,先發送事件給應用程序的主窗口(keyWindow
),主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個處理過程的第一步。
流程圖(圖1):
UIWindow
接收到的事件,有的是經過響應者鏈傳遞,找到合適的響應者進行處理;有的不須要傳遞,直接用 first responder
來處理。這裏咱們主要說須要沿着響應者鏈傳遞的過程。
事件的傳遞大體能夠分爲三個階段:
經過手或觸筆觸摸屏幕所產生的事件,都是經過這三步去傳遞的,如前面提到的觸摸事件和按壓事件。
其實這是肯定第一響應者的過程,第一響應者也就是做爲首先響應這次事件的對象。對於每次事件發生以後,系統會去找能處理這個事件的第一響應者。根據不一樣的事件類型,第一響應者也不一樣:
view
UIKit
指定的那個對象UIKit
指定的那個對象UIKit
指定的那個對象與加速計、陀螺儀、磁力儀相關的運動事件,是不遵循響應鏈機制傳遞的。Core Motion 會將事件直接傳遞給你所指定的第一響應者。
當點擊一個 view
,事件傳遞到 UIWindow
後,會去遍歷 view
層級,直到找到合適的響應者來處理事件,這個過程也叫作 Hit-Test。
既然是遍歷,就會有必定的順序。系統會根據添加 view
的先後順序,肯定 view
在 subviews
中的順序,而後根據這個順序將視圖層級轉化爲圖層樹,針對這個樹,使用倒序、深度遍歷的算法,進行遍歷。之因此要倒敘,是由於最頂層的 view
最有可能成爲響應者。
Hit-Test 在代碼中對應的方法爲:
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
// hitTest 內部調用下面這個方法
func point(inside point: CGPoint, with event: UIEvent?) -> Bool
複製代碼
詳細步驟:
keyWindow
接收到 UIApplication
傳遞過來的事件,首先判斷本身可否接受觸摸事件,若是能,那麼判斷觸摸點在不在本身身上keyWindow
身上,那麼 keyWindow
會從後往前遍歷本身的子控件(爲了尋找最合適的 view
)view
,若是沒有更合適的子控件,那麼本身就是最合適的 view
每當手指接觸屏幕,UIApplication
接收到手指的事件以後,就會去調用 UIWindow
的 hitTest:withEvent:
,看看當前點擊的點是否是在 window
內,若是是則繼續依次調用 subView
的 hitTest:withEvent:
方法,直到找到最後須要的 view
。調用結束而且 hit-test view
肯定以後,這個 view
和 view
上面依附的手勢,都會和一個 UITouch
的對象關聯起來,這個 UITouch
會做爲事件傳遞的參數之一,咱們能夠看到 UITouch
的頭文件中有一個 view
和 gestureRecognizers
的屬性,就是 hit-test view
和它的手勢。
以下圖(圖2):
Hit-Test 是採用遞歸的方法從 view
層級的根節點開始遍歷,來經過一個例子看一下它是如何工做的(圖3):
UIWindow
有一個 MainView
,MainView
裏面有三個 subView
:viewA
、viewB
、viewC
。它們各自有兩個 subView
,它們的層級關係是:viewA
在最下面,viewB
在中間,viewC
最上(也就是 addSubview
的順序,越晚 add
進去越在上面),其中 viewA
和 viewB
有一部分重疊。
若是手指在 viewB.1
和 viewA.2
重疊的方面點擊,按照上面的遞歸方式,順序以下圖所示(圖4):
當點擊圖中位置時,會從 viewC
開始遍歷,先判斷點在不在 viewC
上,不在。轉向 viewB
,點在 viewB
上。轉向 viewB.2
,判斷點在不在 viewB.2
上,不在。轉向 viewB.1
,點在 viewB.1
上,且 viewB.1
沒有子視圖了,那麼 viewB.1
就是最合適的 view
。遍歷到這裏也就結束了。
來看一下 hitTest:withEvent:
的實現原理,UIWindow
拿到事件以後,會先將事件傳遞給圖層樹中距離最靠近 UIWindow
那一層最後一個 view
,而後調用其 hitTest:withEvent:
方法。注意這裏是先將視圖傳遞給 view
,再調用其 hitTest:withEvent:
方法,並遵循如下原則:
point
不在這個視圖內,則去遍歷其餘視圖point
在這個視圖內,可是這個視圖還有子視圖,那麼將事件傳遞給子視圖,而且調用子視圖的 hitTest:withEvent:
point
在這個視圖內,而且這個視圖沒有子視圖,那麼 return self
,即它就是那個最合適的視圖point
在這個視圖內,而且這個視圖沒有子視圖,可是不想做爲處理事件的 view
,那麼能夠 return nil
,事件由父視圖處理另外, UIView
有些狀況下是不能接受觸摸事件的:
userInteractionEnabled = NO
alpha < 0.01
,會直接影響子控件的透明度。alpha
在 0 到 0.01 之間會被當成透明處理注:若是父控件不能接受觸摸事件,那麼子控件就不可能接受到事件。
綜上,咱們能夠得出 hitTest:withEvent:
方法的大體實現以下:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 是否能響應 touch 事件
if !isUserInteractionEnabled || isHidden || alpha <= 0.01 { return nil }
if self.point(inside: point, with: event) { // 點擊是否在 view 內
for subView in subviews.reversed() {
// 轉座標
let convertdPoint = subView.convert(point, from: self)
// 遞歸調用,直到有返回值,不然返回 nil
let hitTestView = subView.hitTest(convertdPoint, with: event)
if hitTestView != nil {
return hitTestView!
}
}
return self
}
return nil
}
複製代碼
用一張圖來表示 hitTest:withEvent:
的調用過程(圖是 OC 語法)(圖5):
肯定了最合適的 view
,接下來就是識別是何種事件,在觸摸事件中,對應的就是何種手勢。Gesture Recognizer
(手勢識別器)是系統封裝的一些類,用來識別一系列常見的手勢,例如點擊、長按等。在上一步中肯定了合適的 view
以後,UIWindow
會將 touches
事件先傳遞給 Gesture Recognizer
,再傳遞給視圖。能夠自定義一個手勢驗證一下。
Gesture Recognizer 擁有的狀態以下:
public enum State : Int {
// 還沒有識別是何種手勢操做(但可能已經觸發了觸摸事件),默認狀態
case possible
// 手勢已經開始,此時已經被識別,可是這個過程當中可能發生變化,手勢操做還沒有完成
case began
// 手勢狀態發生改變
case changed
// 手勢識別完成(此時已經鬆開手指)
case ended
// 手勢被取消,恢復到默認狀態
case cancelled
// 手勢識別失敗,恢復到默認狀態
case failed
// 手勢識別完成,同 end
public static var recognized: UIGestureRecognizer.State { get }
}
複製代碼
Gesture Recognizer 有一套本身的 touches
方法和狀態轉換機制。一個手勢老是以 possible
狀態開始,代表它已經準備好開始處理事件。從該狀態開始,開始識別各類手勢,直到它們到達 ended
、cancelled
或 failed
狀態。手勢識別器會保持在其中的一個最終狀態,直到當前事件序列結束,此時 UIKit 重置手勢識別器並將其返回 possible
狀態。
再來看看觸摸事件的類型:
UILongPressGestureRecognizer
)UIPanGestureRecognizer
)UIPinchGestureRecognizer
)UIScreenEdgePanGestureRecognizer
)UISwipeGestureRecognizer
)UIRotationGestureRecognizer
)UITapGestureRecognizer
)蘋果將手勢識別器分爲兩種大類型,一個是離散型手勢識別器(Discrete Gesture Recognizer),一個是連續型手勢識別器(Continuous Gesture Recognizer)。離散型手勢一旦識別就沒法取消,並且只會調用一次操做事件,而連續型手勢會屢次調用操做事件,而且能夠取消。在以上手勢中,只有點擊手勢(UITapGestureRecognizer
)屬於離散型手勢。
離散型手勢識別示意圖(圖6):
連續型手勢識別的狀態轉換通常可分爲三個階段:
began
或 failed
狀態changed
或 cancelled
狀態ended
狀態以下圖(圖7):
識別出手勢以後,就要肯定由誰來響應這個事件了,最有機會處理事件的對象就是經過 Hit-Test 找到的視圖或者第一響應者,若是兩個都不能處理,就須要傳遞給下一位響應者,而後依次傳遞,該過程與 Hit-Test 過程正好相反。Hit-Test 過程是從上向下(從父視圖到子視圖)遍歷,touch
事件處理傳遞是從下向上(從子視圖到父視圖)傳遞。下一位響應者是由響應者鏈決定的,那咱們先來看看什麼是響應者鏈。
Response Chain,響應鏈,通常咱們稱之爲響應者鏈。在咱們的 app 中,全部的視圖都是按照必定的結構組織起來的,即樹狀層次結構,每一個 view
都有本身的 superView
,包括 controller
的 topmost view
(即 controller
的 self.view
)。當一個 view
被 add
到 superView
上的時候,它的 nextResponder
屬性就會被指向它的 superView
。當 controller
被初始化的時候,self.view
(topmost view
) 的 nextResponder
會被指向所在的 controller
,而 controller
的 nextResponder
會被指向 self.view
的 superView
,這樣,整個 app 就經過 nextResponder
串成了一條鏈,這就是咱們所說的響應者鏈。因此響應者鏈式一條虛擬的鏈,並無一個對象來專門存儲這樣的一條鏈,而是經過 UIResponder
的屬性串聯起來的。
響應者鏈示意圖(圖8):
即(右圖):
initial view
)嘗試處理事件,若是不能處理,則將事件傳遞給其父視圖(superView1
)superView1
嘗試處理事件,若是不能處理,傳遞給它所屬的視圖控制器(viewController1
)viewController1
嘗試處理事件,若是不能處理,傳遞給 superView1
的父視圖(superView2
)superView2
嘗試處理事件,若是不能處理,傳遞給 superView2
所屬的視圖控制器(viewController2
)viewController2
嘗試處理事件,若是不能處理,傳遞給 UIWindow
UIWindow
嘗試處理事件,若是不能處理,傳遞給 UIApplication
UIApplication
嘗試處理事件,若是不能處理,拋棄該事件再附一個蘋果官方的圖(圖9):
在 iOS 中,只有繼承於 UIResponder
的對象、或者它自己才能成爲響應者。不少常見的對象均可以相應事件,好比 UIApplication
、UIViewController
、全部的 UIView
(包括 UIWindow
)。
UIResponder
提供瞭如下方法來處理事件:
// 觸摸事件
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
@available(iOS 9.1, *)
open func touchesEstimatedPropertiesUpdated(_ touches: Set<UITouch>)
// 運動事件
open func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
open func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
// 遠程控制事件
open func remoteControlReceived(with event: UIEvent?)
// 按壓事件
open func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?)
open func pressesChanged(_ presses: Set<UIPress>, with event: UIPressesEvent?)
open func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?)
open func pressesCancelled(_ presses: Set<UIPress>, with event: UIPressesEvent?)
複製代碼
提供如下屬性和方法來管理響應鏈:
// 負責事件傳遞,默認返回 nil,子類必須實現此方法。
open var next: UIResponder? { get }
// 判斷是否能夠成爲第一響應者
open var canBecomeFirstResponder: Bool { get } // default is NO
// 將對象設置爲第一響應者
open func becomeFirstResponder() -> Bool // default is NO
// 判斷是否能夠放棄第一響應者
open var canResignFirstResponder: Bool { get } // default is YES
// 放棄對象的第一響應者身份
open func resignFirstResponder() -> Bool // default is YES
// 判斷對象是否爲第一響應者
open var isFirstResponder: Bool { get }
複製代碼
補充一下 next
:UIResponder
類並不自動保存或設置下一個響應者,該方法的默認實現是返回 nil
。子類的實現必須重寫這個方法來設置下一響應者。UIView
的實現是返回管理它的 UIViewController
對象(若是它有)或其父視圖;UIViewController
的實現是返回它的視圖(self.view
)的父視圖;UIWindow
的實現是返回 UIApplication
另外說一下 UITouch
,對於觸摸事件(對應的對象爲 UITouch
),系統提供了四個方法來處理:
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
@available(iOS 9.1, *)
open func touchesEstimatedPropertiesUpdated(_ touches: Set<UITouch>)
複製代碼
解釋一下 touchesEstimatedPropertiesUpdated(_ touches: Set<UITouch>)
,當沒法獲取真實的 touches 時,UIKit 會提供一個預估值,並設置到 UITouch 對應的 estimatedProperties 中監測更新。當收到新的屬性更新時,會經過調用此方法來傳遞這些更新值。當使用 Apple Pencil 靠近屏幕邊緣時,傳感器沒法感應到準確的值,此時會獲取一個預估值賦給 estimatedProperties 屬性。不斷去更新數據,直到獲取到準確的值。
上面的前四個方法,是由系統自動調用的:
view
只接收到一個 UITouch
對象。當你使用多個手指同時觸摸時,會接收多個 UITouch
對象,每一個手指對應一個。多個手指分開觸摸,會調用屢次 touches
系列方法,每一個 touches
裏面有一個 UITouch
對象super.touchesxxx
方法,不然事件處理就中斷於此,不會繼續傳遞來看一下 UITouch
對象,它保存了事件的相關信息:
// 觸摸事件產生或變化的時間,單位是秒
open var timestamp: TimeInterval { get }
// 當前觸摸事件所處的狀態
open var phase: UITouch.Phase { get }
// 短期內點按屏幕的次數
open var tapCount: Int { get }
// 觸摸產生時所處的視圖
open var view: UIView? { get }
// 觸摸產生時所處的窗口
open var window: UIWindow? { get }
// 依附在 view 上的手勢
open var gestureRecognizers: [UIGestureRecognizer]? { get }
// 使用硬件設備點擊時,以點爲圓心的 touch 半徑,以此肯定 touch 範圍的大小
open var majorRadius: CGFloat { get }
// 半徑公差
open var majorRadiusTolerance: CGFloat { get }
// 一些方法
/** 返回值表示觸摸點在 view 上的位置 調用時傳入的 view 參數爲 nil 的話,返回的是觸摸點在 UIWindow 的位置 */
open func location(in view: UIView?) -> CGPoint
// 記錄了前一個觸摸點的位置
open func previousLocation(in view: UIView?) -> CGPoint
複製代碼
以幾個例子來講明事件傳遞與響應在項目中的運用,其實運用主要是圍繞 hitTest:withEvent:
和 pointInside:
的使用,這裏簡單舉個例子。
touch
區域在實際開發中,有些 button
面積很小,不容易點擊上。這時候你想擴大 button
的響應區域,能夠經過重寫 hitTest:withEvent:
方法實現,以下圖的狀況(圖10):
實現代碼:
class MyButton: UIButton {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !isUserInteractionEnabled || isHidden || alpha <= 0.01 { return nil }
let inset : CGFloat = 45 - 78
let touchRect = bounds.insetBy(dx: inset, dy: inset)
if (touchRect.contains(point)) {
for subView in subviews.reversed() {
let convertdPoint = subView.convert(point, from: self)
let hitTestView = subView.hitTest(convertdPoint, with: event)
if hitTestView != nil {
return hitTestView!
}
}
return self
}
return nil
}
}
複製代碼
或者直接改 pointIndside
方法:
class MyButton: UIButton {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: 45-78, dy: 45-78).contains(point)
}
}
複製代碼
以前沒作過搖一搖,感受還挺好玩的,就放在這裏,其實很簡單。
import UIKit
class ShakeView : UIView {
override var canBecomeFirstResponder: Bool { // 記得重寫這個方法
return true
}
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
print("搖一搖")
}
}
override func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
print("取消")
}
}
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
print("結束")
}
}
}
class ViewController: UIViewController {
lazy var shakeView : ShakeView? = {
let shakeView = ShakeView(frame: view.bounds)
shakeView.backgroundColor = #colorLiteral(red: 0.08779912442, green: 0.6471169591, blue: 0.9447124004, alpha: 1)
return shakeView
}()
override func viewDidLoad() {
super.viewDidLoad()
// 設置支持搖一搖
UIApplication.shared.applicationSupportsShakeToEdit = true
view.addSubview(shakeView!)
shakeView?.becomeFirstResponder()
}
}
複製代碼
來個總結吧。
iOS 中的事件:
事件從產生到系統傳遞到 App 的 keyWindow
:
IOKit.framework
將事件封裝成一個 IOHIDEvent
對象mach port
(IPC 進程間通訊)轉發到 Springboardmach port
(IPC 進程間通訊)轉發給當前 App 的主線程RunLoop
接收到 Springboard 轉發過來的消息以後,觸發對應的 mach port
的 Source1
回調 __IOHIDEventSystemClientQueueCallback()
Source1
回調內部觸發了 Source0
的回調 __UIApplicationHandleEventQueue()
Source0
回調內部,封裝 IOHIDEvent
爲 UIEvent
Source0
回調內部調用 UIApplication
的 +sendEvent:
方法,將 UIEvent
傳給當前 UIWindow
事件傳遞分爲三步:
view
,即第一響應者)touch
事件)1.Hit-Test:
keyWindow
接收到 UIApplication
傳遞過來的事件,首先判斷本身可否接受觸摸事件,若是能,那麼判斷觸摸點在不在本身身上keyWindow
身上,那麼 keyWindow
會倒序遍歷本身的子控件view
,若是沒有,那麼本身就是最合適的 view
能夠看看圖2。
2.Gesture Recognizer:
UIWindow
會首先將 touches
事件傳遞給 Gesture Recognizer
,再傳遞給視圖。
觸摸事件的具體類型有:
UILongPressGestureRecognizer
)UIPanGestureRecognizer
)UIPinchGestureRecognizer
)UIScreenEdgePanGestureRecognizer
)UISwipeGestureRecognizer
)UIRotationGestureRecognizer
)UITapGestureRecognizer
)蘋果又將手勢識別器分爲兩大類型,離散型和連續型,上述類型中只有點擊手勢(UITapGestureRecognizer
)屬於離散型。
手勢識別器擁有的狀態:
public enum State : Int {
// 還沒有識別是何種手勢操做(但可能已經觸發了觸摸事件),默認狀態
case possible
// 手勢已經開始,此時已經被識別,可是這個過程當中可能發生變化,手勢操做還沒有完成
case began
// 手勢狀態發生改變
case changed
// 手勢識別完成(此時已經鬆開手指)
case ended
// 手勢被取消,恢復到默認狀態
case cancelled
// 手勢識別失敗,恢復到默認狀態
case failed
// 手勢識別完成,同 end
public static var recognized: UIGestureRecognizer.State { get }
}
複製代碼
3.Response Chain
事件沿着響應鏈傳遞,傳遞順序與尋找第一響應者的順序正好相反。
傳遞順序:
initial view
)嘗試處理事件,若是不能處理,則將事件傳遞給其父視圖(superView1
)superView1
嘗試處理事件,若是不能處理,傳遞給它所屬的視圖控制器(viewController1
)viewController1
嘗試處理事件,若是不能處理,傳遞給 superView1
的父視圖(superView2
)superView2
嘗試處理事件,若是不能處理,傳遞給 superView2
所屬的視圖控制器(viewController2
)viewController2
嘗試處理事件,若是不能處理,傳遞給 UIWindow
UIWindow
嘗試處理事件,若是不能處理,傳遞給 UIApplication
UIApplication
嘗試處理事件,若是不能處理,拋棄該事件官方文檔 About the Gesture Recognizer State Machine
官方文檔 Implementing a Discrete Gesture Recognizer
官方文檔 Implementing a Continuous Gesture Recognizer
官方文檔 Using Responders and the Responder Chain to Handle Events