競合するUIGestureRecognizerを上手く共存させる

viewに割り当てたジェスチャー同士がバッティングした時にはどれを失敗させるのか、事前に決めておく必要があります。

こういった処理は通常、画面上のジェスチャーを同列に扱えるViewControllerの中で書く訳ですが、複雑なアプリになると出来るだけVCには書きたくなくなります。

そこで、ある程度良く使うダブルタップ縦横のドラッグだけは単独で完結するサブクラスにしておいてStoryboardで使うと便利になります。あとはIBAction書くだけ。

目次

シングルタップと共存できるDoubleTapGestureRecognizer

class DoubleTapGestureRecognizer: UITapGestureRecognizer, UIGestureRecognizerDelegate {

    override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        numberOfTapsRequired = 2
        delegate = self
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        numberOfTapsRequired = 2
        delegate = self
    }

    // ダブルの時にシングルを失敗させる
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

シングル、ダブルに加えてトリプルも共存させる場合はもうちょい工夫が必要ですが、シングルの判定が遅れるので滅多に使わないと思います。

縦スワイプと共存できる横ドラッグHorizontalPanGestureRecognizer

縦スクロールのテーブルに割り当てると干渉せずに横ドラッグだけを検出できます。(セルのスライドメニューが無効な時)

class HorizontalPanGestureRecognizer: UIPanGestureRecognizer, UIGestureRecognizerDelegate {

    override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        delegate = self
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
    }

    // 水平方向のみ有効にする
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        let pan = gestureRecognizer as! UIPanGestureRecognizer
        let trans = pan.translation(in: gestureRecognizer.view)
        return fabs(trans.x) > fabs(trans.y)
    }
}

横スワイプと共存できる縦ドラッグVerticalPanGestureRecognizer

class VerticalPanGestureRecognizer: UIPanGestureRecognizer, UIGestureRecognizerDelegate {

    override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        delegate = self
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
    }

    // 垂直方向のみ有効にする
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        let pan = gestureRecognizer as! UIPanGestureRecognizer
        let trans = pan.translation(in: gestureRecognizer.view)
        return fabs(trans.x) < fabs(trans.y)
    }
}

xibではエラーが出て使えない

カスタムViewのxibで使いたい場合はコードでaddGestureRecognizerしないと使えません。トップレベルのオブジェクトがUIGestureRecognizerではダメですよとエラーが出ます。

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'invalid nib registered for identifier (MyCollectionViewCell) - nib must contain exactly one top level object which must be a UICollectionReusableView instance'

もっと複雑なジェスチャーを混在させる

お互いに相手のジェスチャーを失敗させる設定にしていた場合でも問題が起こらない様に工夫しなくてはなりません。