よくわかるUIGestureRecognizerDelegate

# はじめに
カスタムで作ったUIGestureRecognizerの認識を制御したり、他のジェスチャーとの同時認識や失敗の制御をしたいことは多々あると思います。
その際に、便利なのがUIGestureRecognizerDelegateです。
色々なメソッドが提供されていますが、日本語の資料があまり多くないので、どれがどういった時に役に立つかをまとめてみました。

# UIGestureRecognizerDelegate
UIGestureRecognizerDelegateはアプリのジェスチャー認識の振る舞いを微調整するためのprotocolです。
UIKitのUIGestureRecognizerDelegateには以下のメソッドが提供されています。
すべてoptionalです。

```swift:UIKitから抜粋
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を実装するだけです。例を示します。

```ViewController.swift
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のみを制御させたいときは、下記のように使えます。

```swift
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のクラスによって制御することもできます。
下記に例を示します。

```swift
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によって失敗させられることはありません。

```swift
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    // trueなら他のGestureによって失敗要求が出された場合、失敗する
    return true
}
```

### shouldBeRequiredToFailByメソッド
上に対して、こちらはGesture自身が他のGestureに対して失敗を要求するかどうかを制御します。
一見、英語からすると意味が逆なのではと思う方もいると思いますが、これで正しいです。
このメソッドの返り値をtrueにすることで、ほかのGestureは失敗が要求されます。
注意が必要なのは、他のGestureがshouldRequireFailureOfメソッドによって、失敗されないように制御されていた（falseを返していた）場合、ここでtrueを返しても他のGestureが失敗されることはありません。
このメソッドを実装していない場合は、デフォルトではfalseが返るため、他のGestureに失敗の要求をすることはありません。

```swift
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を正しく理解して、ユーザにとって使いやすい、適切なジェスチャーハンドリングを実装しましょう。

# 参考リンク
* [UIGestureRecognizerDelegate - UIKit | Apple Developer Documentation](https://developer.apple.com/reference/uikit/uigesturerecognizerdelegate)