ドラッグで「戻る」「閉じる」ができるDraggableNavigationControllerを作ってみた

DraggableNavigationContoller この仕組みはiOS 13に標準搭載されましたので、カスタマイズ不要となりました。

ViewControllerの画面遷移を思いのままにカスタマイズ

モーダルの場合はtransitioningDelegateメソッド

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return animationController
}

func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return interactionController
}

タブバーやナビゲーションの遷移は自身のdelegateメソッド

func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return animationController
}

func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return interactionController
}

これを設定するとナビゲーションのエッジスワイプを自動的に無効にしてくれるみたい。

カスタマイズには2種類のクラスを使います

アニメーター(UIViewControllerAnimatedTransitioning)

どんな動きをするか、純粋にアニメーションだけを担当します。
コンテキストから画面遷移に必要な2画面分のVCとviewを取得できる。
戻る場合は前の画面が回転前のままかもしれないので正しいframeをセットしておく。
従来型のanimateだけでなく、animateKeyframesでキーフレームアニメにもできます。
ジェスチャーやインタラクターには関与しない。
これがなければ標準のアニメーションになります。

遷移処理はそれぞれ微妙に異なっていて、pushの逆再生でpopのように簡単にはいかないので多少手間がかかります。

インタラクター(UIPercentDrivenInteractiveTransition)

アニメーションを割合で制御します。
どんなアニメーションかは知らない。
ジェスチャーと仲が良い。
ドラッグ操作にはこれとアニメーターの両方が必要になる。
割合の基準幅は分割されているかもしれないのでスクリーンの幅は使わない。

Appleのサンプルでは、これの内部でジェスチャーをハンドリングさせてますが、VCを渡してやる必要があります。

ドラッグのジェスチャーコントロール

戻る(pop)

テーブルの水平方向のジェスチャーが効いている時は自動的にドラッグ無効

閉じる(dismiss)

ナビゲーションバーもジェスチャーが有効
スクロール中でもドラッグを追跡して一番上に戻ったらdismiss開始

まとめ

今回は少ない機能でしたが、高速操作や横スクロールへの対応、iPadで便利なプレゼンターなどまだまだ改善させる余地があります。業務の合間に人気ライブラリ作ってる方って大変でしょうね。