【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歳になりました。おめでとう自分。

ではでは。

iOS
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS
more