はじめに
iOSではアプリ起動時に外的作用であるイベントを待ち続け、
イベントが来たら1つずつ処理をするイベントループと呼ばれる状態が作られる。
イベントの例としては、画面のタッチ、端末のシェイク、サーバからのプッシュ通知などがある。
これらの通知は一時的にイベントキューという場所に格納され、先入先出の順でイベントループにより処理がおこなわれる。
アプリケーションの共通部分となるUIApplicationオブジェクトはこのイベントループを管理し、
受け取ったイベントを適所にディスパッチ(割り振り)している。
FirstResponderとは
iOSがイベントを検知したときに
一番はじめにイベント処理を試みるUIResponderオブジェクト。
イベント発生時、まずFirstResponderにイベントがディスパッチされ、
FirstResponderでイベントを捌くことができなかった場合に、
Responder Chain(詳細後述)を辿って、処理できるResponderを探す。
FirstResponderになるためには
canResignFirstResponderプロパティをtrueを返すようにし、
becomeFirstResponder()メソッドでFirstResponderになることを宣言する。
import UIKit
class SampleViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// View表示時にFirstResponderになることを宣言
self.becomeFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Viewが非表示時にFirstResponderをやめる
self.resignFirstResponder()
}
// このViewControllerがFirstResponderになれるようにする
override var canBecomeFirstResponder: Bool {
return true
}
}
そもそもUIResponderとは
イベントに応答して処理するための抽象的なインターフェース
より具体的に書くと、
「UILabelなどUIControlのサブクラスではないUI部品において、
iOSが検知したtouchイベントなどを処理するためのクラス」
UIViewやUIViewController、UIViewを継承しているUIWindow、AppDelagate、UIApplicationなどは、UIResponderを継承している。
そのため、UIButtonのように自分自身がタッチされたことを検知できる仕組みを持っていなくても、
UIResponderのメソッドをオーバーライドすることでイベントを処理することができる。
例えばtouchイベントの場合
UIResponderのサブクラスで以下のようなメソッドをオーバーライドすることができる。
touchesBegan(_:with:)
viewやwindowなどに、1本以上の指が触れたときに呼ばれる
func touchesBegan(Set, with: UIEvent?)
touchesMoved(_:with:)
viewやwindowなどで、1本以上の指が動いたときに呼ばれる
func touchesMoved(Set, with: UIEvent?)
touchesEnded(_:with:)
viewやwindowなどで、1本以上の指が離れたときに呼ばれる
func touchesEnded(Set, with: UIEvent?)
touchesCancelled(_:with:)
システムイベント(システムアラートなど)によりタッチがキャンセルされたときに呼ばれる
func touchesCancelled(Set, with: UIEvent?)
Responder Chainとは
iOSが検知したイベントを処理できるオブジェクトが見つかるまで、
イベントを順次リレーする仕組み。
検知されたイベントはUIApplicationオブジェクトからUIWindowオブジェクトへ渡され、
UIWindowオブジェクトはそのイベントに最も相応しいと思われるオブジェクトにそのイベントをディスパッチする。この時、オブジェクトがイベントを処理できない(=ハンドラがない)場合、
処理可能なオブジェクトが見つかるまで上位のスコープにイベントが渡される。
このようなイベントをリレーするオブジェクトの連鎖がResponderChain。
ResponderChainはイベントにより動的に作成される。
例えばtouchイベントの場合、下記のHitTest(当たり判定)を用いてResponder Chainを形成する。
HitTestの流れ
UIViewが持つhitTest(_ : withEvent)メソッドが、
その内部でpointInside(_ : withEvent)メソッドを呼び
自分のsubViewのうちどれがタッチされたのかを返す
例として下記の図では、
ViewA上にあるViewCをタッチした場合のHitTestを行なっている。
まとめ
FirstResponderは一番はじめにイベント処理を試みるUIResponderオブジェクトとして
システムに明示的に伝えるもの。
iOSで検知されたイベントはFirstResponderに渡され、
FirstResponderが処理できない場合はResponderChainを使って、次のオブジェクトにイベントがリレーされる。
間違いやご指摘などありましたら、コメントにてご教授いただけますと幸いです。