内容概覽
- 綜述
- 确定一個事件的第一響應者(First Responder)
- 确定哪個響應者包含一個觸控事件
- 改變響應者鍊
- 在視圖(UIView)中處理觸控事件
綜述
iOS 應用使用響應者對象接收和處理事件。一個響應者對象是
UIResponder
類的執行個體,常見的子類包括:
UIView
,
UIViewController
,
UIApplication
。響應者接收原始的事件資料,并且必須對其進行 處理 或者将其 轉發給另一個響應者對象。當你的應用接收到一個事件時,
UIKit
自動将這個事件傳遞給最适合處理這個事件的響應者對象 —— 第一響應者(
first responder
)。
未被處理的事件會在響應鍊中的響應者之間傳遞,這是應用中的響應者對象的動态配置。
上圖展示了一個應用中的響應者,還顯示了事件如何按照響應者鍊從一個響應者傳遞到下一個響應者。
如果 text field 不處理事件,則 UIKit 會将事件發送到 text field 的 super view 對象,然後是 window 的 root view。
從 root view 開始,響應鍊在将事件傳遞到 window 之前,先傳遞到 root view 所屬的 view controller。
如果 window 無法處理事件,則 UIKit 會将事件傳遞給 UIApplication 對象,如果該 UIApplication 對象是 UIResponder 執行個體并且還不是響應者鍊的一部分,則可能傳遞給應用程式委托。
确定一個事件的第一響應者(First Responder)
UIKit 根據事件的類型将對象指定為事件的第一響應者。
事件類型 | 第一響應者 |
---|---|
觸控事件 | 發生觸控事件的視圖 |
按下裝置的實體按鍵觸發的事件 | 有焦點的對象 |
搖晃動作事件 | 由 UIKit 或者開發者指定 |
遠端控制事件 | 由 UIKit 或者開發者指定 |
編輯菜單消息 | 由 UIKit 或者開發者指定 |
與加速度計、陀螺儀和磁力計有關的運動事件不遵循響應程式鍊。
相反,
Core Motion
将這些事件直接傳遞到指定的對象。
有關更多資訊,請參見 Core Motion Framework。
定義事件類型的代碼:
public enum EventType : Int {
// 點選螢幕相關的事件
case touches
// 裝置移動相關的事件,如:使用者搖晃裝置
case motion
// 來自控制裝置多媒體的外部配件,如:耳機
case remoteControl
// 按下裝置的實體按鍵
@available(iOS 9.0, *)
case presses
}
控件(UIControl)使用動作(action)消息直接與其關聯的目标對象進行通信。
// 為特定事件添加 target/action。你可以多次調用,也可以為特定事件指定多個 target/action
// 當 target 為nil時,事件沿着響應者鍊傳遞。action 可能會包括發送者和事件的順序
// action 不能為NULL。target 不會被強引用
func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event)
當使用者與控件互動時,控件會将操作消息發送到其目标對象(target)。
動作消息不是事件,但它們仍可以利用響應者鍊。當控件的目标對象為
nil
時,
UIKit
從目标對象開始并周遊響應者鍊,直到找到實作适當操作方法的對象為止。
例如,
UIKit
編輯菜單使用此行為來搜尋響應者對象,這些對象實作了諸如 cut(😃, copy(😃, or paste(_😃 之類的方法。
手勢識别器(Gesture recognizer) 會在視圖處理事件之前接收觸摸(touch)和按下(press)事件。
如果視圖的手勢識别器無法識别一系列觸摸,則
UIKit
會将觸摸發送給視圖。
如果視圖無法處理觸摸,則
UIKit
會将其向上傳遞到響應者鍊。
有關使用手勢識别器處理事件的更多資訊,請參見處理 Handling UIKit Gestures。
确定哪個響應者包含一個觸控事件
UIKit
使用基于視圖的點選測試來确定觸摸事件發生的位置。
具體來說,
UIKit
将觸摸位置與視圖層次結構中視圖對象的邊界(bounds)進行比較。
UIView
的
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
方法周遊視圖層次結構,查找包含指定觸摸的最深的子視圖,該子視圖會成為觸摸事件的第一響應者。
如果觸摸位置在視圖範圍(bounds)之外,則方法将忽略該視圖及其所有子視圖。是以,如果視圖的
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
屬性為
clipsToBounds
false
,則即使該視圖正好包含觸摸,也不會傳回該視圖範圍(bounds)之外的子視圖。
有關點選測試行為的更多資訊,請參見UIView中關于
方法的讨論。
hitTest(_:with:)
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
方法的文檔:
- 讨論解析:
此方法通過調用每個子視圖的
point(inside:with:)
方法來周遊視圖層次結構,以确定哪個子視圖應接收觸摸事件。
// 檢測該點是否在接收者的 bounds 範圍内
func point(inside point: CGPoint, with event: UIEvent?) -> Bool
如果
point(inside:with:)
傳回
true
,則将周遊子視圖的視圖層次結構,直到找到最前面的包含指定點的視圖。如果視圖不包含該點,則将忽略其視圖層次結構的分支。
你很少需要自己調用此方法,但是你可以重寫此方法以在子視圖中隐藏觸摸事件。
此方法将忽略 隐藏的、禁用使用者互動的、alpha值小于0.01 的視圖對象。
确定點選時,此方法不會考慮視圖的内容。即使指定點位于該視圖内容的透明部分中,該視圖仍然可以被傳回。
位于接收者範圍之外的點,即使實際上位于接收者子視圖内,也不會被點選測試比對。如果目前視圖的
clipsToBounds
屬性設定為
false
,并且受影響的子視圖超出了視圖的範圍,則可能發生這種情況。
發生觸摸時,
UIKit
會建立一個
UITouch
對象并将其與視圖關聯。
随着觸摸位置或其他參數的更改,
UIKit
會使用新資訊更新相同的UITouch對象。但唯一不變的屬性是視圖。 (即使觸摸位置移到原始視圖之外,UITouch 對象的 view 屬性中的值也不會更改。)
觸摸結束時,
UIKit
會釋放該
UITouch
對象。
改變響應者鍊
您可以通過重寫響應者對象的
next
屬性來更改響應者鍊。
如果重寫,下一個響應者将會是你傳回的對象。
許多
UIKit
類已經重寫此屬性并傳回特定的對象,比如:
-
對象。如果 view 是 view controller 的 root view,則下一個響應者是 view controller;否則,下一個響應者是 view 的 super view。UIView
-
對象UIViewController
- 如果 view controller 的 view 是 window 的 root view,則下一個響應者是 window 對象。
- 如果 view controller B 是由 view controller A present的,則下一個響應者是 view controller A。
-
對象。window 的下一個響應者是UIWindow
對象。UIApplication
-
對象。下一個響應者是 app delegate,但僅當 app delegate 是 UIResponder 的執行個體且不是 view,view controller 或 UIApplication 對象本身時,才是下一個響應者。UIApplication
在視圖(UIView)中處理觸控事件
如果你不打算在
UIView
中使用
UIGestureRecognizer
來處理觸控事件,你可以使用
UIView
本身來處理。
因為
UIView
繼承于
UIResponder
,是以它可以處理多點觸控事件和其他類型的事件。
當
UIKit
确定一個事件發生在某個
UIView
上時,它會調用該 view 的 touchesBegan:withEvent:, touchesMoved:withEvent:, 或者 touchesEnded:withEvent: 等方法。
你在
UIView
中重寫的這些方法将處理點選事件處理過程中不同的階段。
點選事件的不同階段,如下圖所示:
當手指(或者蘋果筆)點選螢幕時,
UIKit
會建立一個
UITouch
對象,然後将其觸控位置設定為合适的點,将其
phase
屬性設定為
UITouchPhaseBegan
。
當同一個手指在螢幕上移動時,
UIKit
會更新該
UITouch
對象的觸控位置并将
phase
屬性設定為
UITouchPhaseMoved
。
當手指擡起并離開螢幕,
UIKit
會将
phase
屬性設定為
UITouchPhaseEnded
,然後點選事件結束。
類似地,系統可能會在任何時候取消正在進行的點選事件。比如,電話呼入可以打斷應用的執行過程。
這時,
UIKit
會通過調用
touchesCancelled:withEvent:
方法通知你的 view。
你可以通過該方法來清理 view 中的資料結構。
UIKit
會為每個點選螢幕的新手指建立
UITouch
對象。這些
UITouch
對象會和目前的
UIEvent
對象一起被傳遞。
UIKit
可以區分來自手指和蘋果筆的觸控,是以你可以對它們做不同的處理。
預設情況下,一個 view 隻接收第一個
UITouch
和相應的事件,即使不隻一個手指點選了螢幕。
如果需要接收多個觸控,你需要将
設定為
multipleTouchEnabled
。
true
參考内容:
Using Responders and the Responder Chain to Handle Events
Handling Touches in Your View
轉載請注明出處,謝謝~