うるおいらんど

【Swift】UINavigationControllerのスワイプでPop時の移動量を取得してみた

お久しぶりです。Reoです。

今回は、UINavigationControllerでSwipeで前の画面に戻っているときのイベントを取得してみました。

上記GIFのスワイプ時にどれだけ移動しているかを取得したくて結構数日かけて悩んでました。

実装自体はとても簡単です。

 

Popジェスチャー時のアクションを追加する

これを見つけるのにどれだけ時間がかかったことか・・・。

override func viewDidLoad() {
    super.viewDidLoad()

    self.navigationController?.interactivePopGestureRecognizer?.addTarget(self, action: #selector(self.navigationPopGestureChanged(sender:)))
}

UINavigationControllerが付いているViewController内のviewDidLoadにでも上記を追加します。

ほいで、呼び出す関数も書いておきます。

@objc func navigationPopGestureChanged(sender:UIGestureRecognizer) {
    // PopGesture中に行いたい処理
}

とりあえずこれだけで、Popジェスチャー中に処理を追加することができます。

 

移動量を取得してみる

移動量を取得してみます。

@objc func navigationPopGestureChanged(sender:UIGestureRecognizer) {
    let point = sender.location(in: self.view)
    print(point)
}

これで現在タップしている座標が取得できます。

ちなみに私がこれを用いてやりたかったことはLargeTitleViewを利用している時のような挙動(以下画像)でして、似たような実装をするためには、PopGesture中の移動量を取得する必要があります。

 

先程のx座標を用いて

@objc func navigationPopGestureChanged(sender:UIGestureRecognizer) {
    let width = self.view.frame.width
    let point = sender.location(in: self.view)
    let alpha = point.x / (width - point.x)
    hogeView.alpha = alpha
}

のようにすることで、スワイプの量に合わせてhogeViewを表示・非表示することができます。

厳密に言えば、sender.location(in: self.view)にて取れる数値は実際に画面端からの移動量ではなく、あくまでタップしている指の位置になります。ですが、このpopジェスチャー自体が画面の端が始点になっているのでそこまで気を使わなくてもいいかなーという気はしています。

 

ViewControllerを判別する

実際に上のように書いていた場合、どこのViewControllerでPopジェスチャーをした場合にもnavigationPopGestureChanged(sender:)が呼び出されるようになります。

特定のViewControllerを表示しようとしている時だけを判別するときは、visibleViewControllerを用いると良いでしょう。

@objc func navigationPopGestureChanged(sender:UIGestureRecognizer) {
    // Popでの戻り先のViewControllerがselfのときだけ
    if navigationController?.visibleViewController != self {
        return
    }
}

FirstVC(self) -> SecondVC -> ThirdVC となっているNavigationControllerの場合、 ThirdVC -> SecondVCにpopする場合はSecondVCが SecondVC -> FirstVCにpopする場合はFirstVCがvisibleViewControllerになります。

そのため、上記のように書いた場合は、SecondVC -> FirstVCにpopするときにのみ実行するようになります。

あとはnavigationController?.viewControllersのカウントだったりで判定しても良いかな?という感じです。

 

指が離された時の実装

これは、上記実装だけでは賄えない気がしています。

一応指が離された時にも一度呼ばれますが(sender.numberOfTouchesを調べると0が一度呼ばれる)、それだけでは、どちらに遷移したとかは取得できません。

 

なので、UINavigationControllerDelegateを用います。

override func viewDidLoad() {
    super.viewDidLoad()

    self.navigationController?.delegate = self
    self.navigationController?.interactivePopGestureRecognizer?.addTarget(self, action: #selector(self.navigationPopGestureChanged(sender:)))
}

extensionで以下を追加

extension FirstViewController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        // 戻るボタンを押したとき用
        if viewController == self {
            hogeView.alpha = 1
        }else {
            hogeView.alpha = 0
        }
        
        // popGesture時にキャンセルしたかどうかを判別できる
        viewController.transitionCoordinator?.notifyWhenInteractionChanges { context in
            if viewController != self {
                return
            }
            if context.isCancelled { // 元の画面に戻る
                self.hogeView.alpha = 0
            }else { // pop完了
                self.hogeView.alpha = 1
            }
        }
    }
}

このようにしてあげると、指を離したときにどちらに遷移したかを取得することができます。(参照

 

実装画面は諸事情で割愛します。

以上でする。

 

こうやって書くとしょうもない気もしますが、まじで結構悩みました。

navigationController?.interactivePopGestureRecognizer?.addTarget

これだけ覚えておけば、全く記事が出てこないわけではないので是非。

自分が NavigationController swipe gesture offsetとかで調べても全然出てこなかったので本当つらかった。結構色々いじり倒して己の力でたどり着きましたわ・・・。

LargeTitleViewの上にaddSubviewする大分やばそうな技も使ってみましたが、結局やめました。アレはアレで全く使い道ないこともないと思うけど、高さがうまく変えられないのがツライです。

っていうかあのLargeTitleViewの動きすげーわ。ボタンがタイトルに変わる技を使うのは流石に無理だー。

 

そういえば24歳になりました。おめでとう自分。

ではでは。

参考リンク

Comments

コメントはありません。

現在コメントフォームは工事中です。