はじめに
カスタムで作ったUIGestureRecognizerの認識を制御したり、他のジェスチャーとの同時認識や失敗の制御をしたいことは多々あると思います。
その際に、便利なのがUIGestureRecognizerDelegateです。
色々なメソッドが提供されていますが、日本語の資料があまり多くないので、どれがどういった時に役に立つかをまとめてみました。
UIGestureRecognizerDelegate
UIGestureRecognizerDelegateはアプリのジェスチャー認識の振る舞いを微調整するためのprotocolです。
UIKitのUIGestureRecognizerDelegateには以下のメソッドが提供されています。
すべてoptionalです。
public protocol UIGestureRecognizerDelegate : NSObjectProtocol {
// Gestureの認識を開始させるかの制御
@available(iOS 3.2, *)
optional public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
// Gestureの同時認識を許可するかの制御
@available(iOS 3.2, *)
optional public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
// 自身のGestureまたは他方のGestureを失敗させるかの制御
@available(iOS 7.0, *)
optional public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool
@available(iOS 7.0, *)
optional public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool
// GestureのTouchを受け取るかの制御
@available(iOS 3.2, *)
optional public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
// GestureのPressを受け取るかの制御
@available(iOS 9.0, *)
optional public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive press: UIPress) -> Bool
}
使い方
使い方は簡単で、UIGestureRecognizerに対してプロパティのdelegateに受け取りたい先のインスタンス(基本的にVCになると思います)をセットし、UIGestureRecognizerDelegateを実装するだけです。例を示します。
class ViewController: UIViewController {
var gesture: UIGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
// gestureの初期化処理を行う
// delegateのセット
gesture?.delegate = self
}
}
extension ViewController: UIGestureRecognizerDelegate {
// ここに必要なdelegateメソッドを実装していく
}
それぞれのdelegateメソッドの詳細
ここからは、それぞれのdelegateメソッドの使い方を以下の3つの制御分類に分けて、例とともに紹介していきます。
- 認識制御系メソッド
- 同時認識制御系メソッド
- 失敗制御系メソッド
認識制御系メソッド
gestureRecognizerShouldBeginメソッド
Gestureの認識を開始させるかの制御を行いたいときは、gestureRecognizerShouldBegin()メソッドを実装します。
UIGestureRecognizerDelegateのメソッドの中でも一番使用用途が多いかもしれません。
UIGestureRecognizerはstateプロパティを持っており、.possible, .began, .changed, .ended, .cancelled, .failedといった具合に認識状況に応じて変化していきます。
このstateでいうところの、.possibleから.beganになるべきか.failedになるべきかをこのメソッドで制御できます。
このメソッドを実装しない場合は、デフォルトでtrueが返ります。
例えば、特定のgestureのみを制御させたいときは、下記のように使えます。
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === self.gesture { // 特定のgestureを捕捉して認識させるか制御
// hogehoge
}
return true
}
shouldReceive touchメソッド
touchesBegan()より前に呼ばれるメソッドで、touchを受け取るかどうかを制御します。
trueを返すと、その後touchesBegan()メソッドが呼ばれます。falseの場合は呼ばれません。
このviewからのtouchは受け取らないといった制御を入れたい時に使うことができます。
shouldReceive pressメソッド
iOS9から利用可能になったメソッドです。
pressesBegan()より前に呼ばれるメソッドで、pressを受け取るかどうかを制御します。
trueを返すと、その後pressesBegan()メソッドが呼ばれます。falseの場合は呼ばれません。
このviewからのtouchは受け取らないといった制御を入れたい時に使うことができます。
同時認識制御系メソッド
shouldRecognizeSimultaneouslyWithメソッド
このメソッドは、複数のGestureを同時認識させたい時に使います。
例えば、VCが持つviewの上に貼ったUIScrollView系のViewがいる場合を考えます。
認識の優先度的には、最前面のGestureが認識されるので、ScrollViewの中に設定されているGestureの方が優先されてしまい、VCが持つviewにaddしたカスタムGestureはこのままでは、認識できません。
そこで、このメソッドを用いて、同時認識を許可してあげることによって、カスタムのGestureも認識させることができるようになります。
使い方は簡単で、同時認識させたい場合は、trueを返してあげればよいです。
逆に同時認識をさせたくない場合はfalseを返しましょう。このメソッドを実装しない場合はデフォルトでfalseが返ります。
このメソッドでは、競合するGestureをotherGestureRecognizerで取得できるので、そのGestureのクラスや親のViewのクラスによって制御することもできます。
下記に例を示します。
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === self.gesture { // 特定のgestureのとき
// hogehoge
}
if otherGestureRecognizer is UIPanGestureRecognizer { // 他方がPan(ドラッグ)Gestureのとき
// hogehoge
}
if otherGestureRecognizer.view is UIScrollView { // 他方のGestureの親がUIScrollViewのとき
// hogehoge
}
return false
}
失敗制御系メソッド
これらは、2つのGestureの認識が競合した時に自身が失敗をするか/他方の失敗をさせるか制御するメソッドです。
どっちがどっちだっけ?と混同しやすいので注意が必要です。
shouldRequireFailureOfメソッド
このメソッドは、delegateをセットしたGesture自身が他のGestureによって失敗されるかどうかを制御します。
もう一つの失敗制御メソッドのshouldBeRequiredToFailByと混同しやすいので注意してください。
このメソッドの返り値をtrueにすることで、他のGestureによって自身のGestureが失敗させられることを許可します。
もちろん、他のGestureが失敗を要求しない限りは自身のGestureが失敗させられることはありません。
このメソッドを実装していない場合は、デフォルトではfalseが返るため、他のGestureによって失敗させられることはありません。
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// trueなら他のGestureによって失敗要求が出された場合、失敗する
return true
}
shouldBeRequiredToFailByメソッド
上に対して、こちらはGesture自身が他のGestureに対して失敗を要求するかどうかを制御します。
一見、英語からすると意味が逆なのではと思う方もいると思いますが、これで正しいです。
このメソッドの返り値をtrueにすることで、ほかのGestureは失敗が要求されます。
注意が必要なのは、他のGestureがshouldRequireFailureOfメソッドによって、失敗されないように制御されていた(falseを返していた)場合、ここでtrueを返しても他のGestureが失敗されることはありません。
このメソッドを実装していない場合は、デフォルトではfalseが返るため、他のGestureに失敗の要求をすることはありません。
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// trueなら他のすべての競合するGestureに対して失敗要求を出す
return true
}
Tips: UIKitのクラスで使われているGestureはdelegateをセットできない
カスタムで作ったGestureRecognizerではなく、UIKitの中で使われているアクセス可能なGestureのdelegateも実装したいと考える方もいると思います。
が、これらのGestureのdelegateをセットした場合、エラーが出てクラッシュしてしまうので注意が必要です。
下記は、UITableView(UIScrollView)のpanGestureRecognizerのdelegateをセットした際に実行した時のエラーログです。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.'
まとめ
UIGestureRecognizerDelegateを正しく理解して、ユーザにとって使いやすい、適切なジェスチャーハンドリングを実装しましょう。