iOSの写真アプリの画面遷移
iOSの写真アプリの画面遷移は使いやすいなと思っています。
写真一覧から写真をタップするとズームして、下に少し引っ張ると戻れるというやつです。
この画面遷移とインタラクションをコピーして実装してみました。
この記事でできるもの
サンプルコードはGitHubのリポジトリに置いてあります。
masamichiueta/SimplePhotoViewer
実装
いくつかのステップに分けて解説します。
- ViewControllerの構成を考える
- ズームするアニメーションを実装する
- パンジェスチャーで戻るインタラクティブな画面遷移を実装する
1.ViewControllerの構成を考える
ざっくりViewControllerを図示するとこんな感じです。
登場するViewController
は4つです。
- 写真一覧を表示する
CollectionViewController
- 写真のページングを行う
PageViewController
- 2のページで表示する画像の拡大縮小を行う
ZoomImageViewController
- 2のページをラップするコンテナの
ContainerViewController
4はなくてもいいのですが、UIPageViewController
の上にツールバーを置いたりしたいときに、Storyboard上では置けないため、一旦4をコンテナにします。
1のCollectionViewのセルをタップすると画面遷移して、遷移後はUIPageViewController
を使って画像のページングを行います。
2のUIPageViewController
は、UIPageViewControllerDelegate
で3のViewController
のインスタンスを作って、画面として表示します。
実際のストーリーボードはUITabBarController
やUINavigationController
があるのでこんな感じです。
2. ズームするアニメーションを実装する
アニメーションは、カスタムアニメーションを実装する際に実装するUIViewControllerTransitioningDelegate
とUIViewControllerAnimatedTransitioning
を使って実現します。
ズームするアニメーションは、2つのクラスを使います。
ZoomTransitionController
: UIViewControllerTransitioningDelegate
または、UINavigationControllerDelegate
を実装していて、画面遷移を管理する
ZoomAnimator
: UIViewControllerAnimatedTransitioning
を実装していて、ズームするロジックを担当する
ZoomTransitionController
がZoomAnimator
をプロパティとして持っています。
ZoomTransitionController
とZoomAnimator
を別のクラスにしているのは、ZoomTransitionController
が、インタラクティブな遷移とそうでない遷移を管理するためです。ZoomAnimator
はインタラクティブではない遷移となります。
ZoomAnimator
ZoomAnimator
は、ズームアニメーションのロジックを担当します。つまり、ズーム対象のUIImageView
を取得し、遷移元のフレームから遷移先のフレームへとアニメーションを行います。
遷移時の画像とそのフレームを取得にはデリゲートを使います。このデリゲートは遷移元・遷移先となるViewController
が実装します。
ZoomAnimator
は、以下のプロパティを持ちます。
- 遷移元のデリゲート: fromDelegate
- 遷移先のデリゲート: toDelegate
- 一覧からズームインして詳細を表示しているのか、詳細からズームアウトして一覧を表示しているのかを判定: isPresenting
- アニメーション時の画像: transitionImageView
ここからは、ズームのアニメーションを記述していきます。
まず、UIViewControllerAnimatedTransitioning
の2つのメソッドはこうなります。
transitionDuration
の方はアニメーションの長さです。
animateTransition
が実際にアニメーションを行うところになります。ここで、ズームインなのか、ズームアウトなのかを判定して、メソッドを切り替えます。
アニメーションロジック
次に実際のアニメーション部分を実装していきます。
ロジックとしては、以下のような流れです。
- 遷移元、遷移先の画像を非表示にする
- 遷移元の画像から、アニメーション用の画像を作成する
- 遷移先の画像のフレームを計算する
- アニメーション用の画像を、遷移元のフレームから遷移先のフレームへとアニメーションする
ズームイン
ズームアウト
以上でアニメーションのロジックは完成です。
ZoomTransitionController
ZoomTransitionController
はZoomAnimator
が遷移元・遷移先をデリゲートとして参照するために、同じプロパティを持ちます。
ZoomTransitionController
が遷移開始時にZoomAnimator
を返します。
これで、ズームのアニメーションの設定は完了しました。
あとは、ViewController
でZoomTransitionController
を使って、実際にズームの遷移を行います。
ViewController
ZoomTransitionController
を使うために、ContainerViewController
はZoomTransitionController
をプロパティに持ちます。
CollectionView
側では、セルがタップされた時にセグエを実行して画面遷移を実行します。画面遷移時には、prepare
でデリゲートの設定を行います。
これで、UINavigationController
の画面遷移が起こった時にZoomTransitionController
で設定した画面遷移が実行されるようになりました。
残るは、ZoomAnimatorDelegate
を各画面で実装して、遷移元・遷移先の画像とフレームをZoomAnimator
に教えてあげれば完了です。
これで、ズームイン・ズームアウトのアニメーションができるようになりました。
3. パンジェスチャーで戻るインタラクティブな画面遷移を実装する
iOSの写真アプリのように下に引っ張って写真一覧に戻るには、パンジェスチャーによるインタラクティブな画面遷移を実装する必要があります。
今回は、ContainerViewController
にUIPanGestureRecognizer
をつけて、下に引っ張る動作を検出し、インタラクティブな画面遷移を実行します。
iOS写真アプリのインタラクティブな遷移
iOS写真アプリのインタラクティブな遷移は
- 写真をズームしていないときに下に引っ張ると、写真が徐々に小さくなる。最小サイズが存在する
- 写真を下に引っ張ると指の位置に写真が付いてくる
- 写真を下に引っ張る量に応じて背景が透けていく
- 上に向けて指を離すと写真がもとの位置に戻る
なかなか難しそうですよね。これを一つずつ実装していきます。
全体像
まず実装の全体像です。
- インタラクティブな画面遷移のアニメーションを担当する新しいクラス
ZoomDismissalInteractionController
を導入します。
- 先ほど実装した
ZoomTransitionController
には、インタラクティブな画面遷移を実行できるようにZoomDismissalInteractionController
とデリゲートメソッドを追加します。
- パンジェスチャーを使ってインタラクティブな画面遷移を実行するのは、
PageViewController
になります。そのため、PageViewController
にはUIPanGestureRecognizer
を付けます。
ZoomDismissalInteractionController
ZoomDismissalInteractionController
はUIViewControllerInteractiveTransitioning
を実装していて、インタラクティブな画面遷移のロジックを担当します。
ZoomDismissalInteractionController
は、ユーザーがパンジェスチャーを行うたびに呼ばれるメソッドを持っていて、現在のパンジェスチャーの状態によって写真のアニメーションを制御していきます。
ZoomTransitionControllerにZoomDismissalInteractionControllerとデリゲートメソッドを追加する
ZoomDismissalInteractionController
をプロパティに追加し、インタラクティブな画面遷移の時は、こちらの画面遷移ロジックを使用するようにします。
また、インタラクティブな画面遷移を実装するには、UIViewControllerTransitioningDelegate
もしくは、UINavigationControllerDelegate
のメソッドを追加実装する必要があります。
- UIViewControllerTransitioningDelegate
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
- UINavigationControllerDelegate
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
上記のメソッドをZoomTransitionController
に実装していきます。
以上でインタラクティブな画面遷移のベースはできました。あとは実際に画面からジェスチャーを利用してこの画面遷移を実行するだけです。
UIPanGesgureRecognizerを付けて、実際にインタラクティブな画面遷移を実行する
ズームされた画像を下に引っ張って戻れるようにするには、PageViewController
にUIPanGestureRecognizer
を付けて、パンジェスチャーを認識できるようにする必要があります。
ContainerViewController
からPageViewController
にUIPanGestureRecognizer
を付けます。
これでPageViewController
がパンジェスチャーを認識できるようになりました。
パンジェスチャーの処理部分の流れです。
- 処理開始時にインタラクティブな遷移である設定を行う
- パンジェスチャーの実行時は、
ZoomTransitionController
のdidPanWith
メソッドを読んでインタラクティブな処理を行う
- ジェスチャーが終わったら終了時の設定を行い、
ZoomTransitionController
で画面遷移を行う
以上でインタラクティブな画面遷移の設定が完了しました。
まとめ
記事中では全てのソースコードを載せていないのでわかりにくいところもあったかもしれませんが、ぜひ実際にサンプルを動かしてみてください。
この動きを実現するために結構試行錯誤した結果なので、参考になるのではと思います。
masamichiueta/SimplePhotoViewer
サンプルでは、タップすると全画面にしたり、ナビゲーションバーの表示・非表示を制御したり、画像のズームを制御したりなど、色々と説明していない部分も実装してあります。
ズームする画像の動きはライブラリを使えばすぐ実装できますが、自分で実装してみると学びも多いと思います。
実際のiOSの写真アプリは
- 画像のピンチアウトで一覧に戻ったり、
- 画像を上に引っ張ると画像の撮影位置、メモリーズが出てきたり
とさらに複雑なことをしていて、ビューの構成がさらに複雑なのだと思います。
さらに研究を重ねていきたいと思います。
こちらの画面遷移は私が開発しているアプリ
などで使用しているので、ぜひ使ってみてください!