天天看點

iOS 使用響應者和響應者鍊處理事件

内容概覽

  • 綜述
  • 确定一個事件的第一響應者(First Responder)
  • 确定哪個響應者包含一個觸控事件
  • 改變響應者鍊
  • 在視圖(UIView)中處理觸控事件

綜述

iOS 應用使用響應者對象接收和處理事件。一個響應者對象是

UIResponder

類的執行個體,常見的子類包括:

UIView

,

UIViewController

,

UIApplication

。響應者接收原始的事件資料,并且必須對其進行 處理 或者将其 轉發給另一個響應者對象。當你的應用接收到一個事件時,

UIKit

自動将這個事件傳遞給最适合處理這個事件的響應者對象 —— 第一響應者(

first responder

)。

未被處理的事件會在響應鍊中的響應者之間傳遞,這是應用中的響應者對象的動态配置。

iOS 使用響應者和響應者鍊處理事件

上圖展示了一個應用中的響應者,還顯示了事件如何按照響應者鍊從一個響應者傳遞到下一個響應者。

如果 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?

方法的文檔:

iOS 使用響應者和響應者鍊處理事件
  • 讨論解析:

此方法通過調用每個子視圖的

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

類已經重寫此屬性并傳回特定的對象,比如:

  • UIView

    對象。如果 view 是 view controller 的 root view,則下一個響應者是 view controller;否則,下一個響應者是 view 的 super view。
  • UIViewController

    對象
    • 如果 view controller 的 view 是 window 的 root view,則下一個響應者是 window 對象。
    • 如果 view controller B 是由 view controller A present的,則下一個響應者是 view controller A。
  • UIWindow

    對象。window 的下一個響應者是

    UIApplication

    對象。
  • UIApplication

    對象。下一個響應者是 app delegate,但僅當 app delegate 是 UIResponder 的執行個體且不是 view,view controller 或 UIApplication 對象本身時,才是下一個響應者。

在視圖(UIView)中處理觸控事件

如果你不打算在

UIView

中使用

UIGestureRecognizer

來處理觸控事件,你可以使用

UIView

本身來處理。

因為

UIView

繼承于

UIResponder

,是以它可以處理多點觸控事件和其他類型的事件。

UIKit

确定一個事件發生在某個

UIView

上時,它會調用該 view 的 touchesBegan:withEvent:, touchesMoved:withEvent:, 或者 touchesEnded:withEvent: 等方法。

你在

UIView

中重寫的這些方法将處理點選事件處理過程中不同的階段。

點選事件的不同階段,如下圖所示:

iOS 使用響應者和響應者鍊處理事件

當手指(或者蘋果筆)點選螢幕時,

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

轉載請注明出處,謝謝~