[iOS] 遷移元の画像が遷移先にズームイン・アウトするような画面遷移(UITableView及びUICollectionViewの場合)
1 はじめに
テーブルビューやコレクションビューに一覧されている写真などを選択すると、ズームして表示されるような画面遷移を試してみました。
最初に、実行しているようすです。
2 画面遷移のカスタマイズ
present modallyで画面遷移をカスタマイズする手順は、下記のとおりです。
- 遷移を表現するクラスを作成 UIViewControllerAnimatedTransitioning
- 1.を指定するdelegateクラスを作成 UIViewControllerTransitioningDelegate
- 遷移先ビューのtransitioningDelegateに、2.を設定する
今回は、1.及び2.を実装したクラスCustomTransitionを作成しました。
class CustomTransition: NSObject, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning { // ・・・・ }
(1) UIViewControllerTransitioningDelegate
UIViewControllerTransitioningDelegateプロトコルを実装したクラスでは、animationController(forPresented:presenting:source:)及びanimationController(forDismissed:)を実装し、それぞれ、遷移と復帰の処理を実装したクラスを返します。
サンプルでは、遷移か復帰かのフラグをセットし、自分自身を返しています。
// MARK: - UIViewControllerTransitioningDelegate public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { isPresent = true return self } public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { isPresent = false return self }
(2) UIViewControllerAnimatedTransitioning
UIViewControllerAnimatedTransitioningプロトコル実装したクラスでは、遷移にかかる時間を返すtransitionDuration(using:)と、遷移時のアニメーション等(本体)animateTransition(using:)が必要です。
// MARK: - UIViewControllerAnimatedTransitioning func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.7 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { if isPresent { presentTransition(transitionContext: transitionContext) } else { dissmissalTransition(transitionContext: transitionContext) } }
遷移処理の本体は、presentTransition(transitionContext:)に、復帰処理の本体は、dissmissalTransition(transitionContext:)によれぞれ実装しました。
3 ズーム処理
ズームでの登場人物は次の5人です。
① ズーム前のイメージ
② アニメーション用のイメージ
③ ズーム後のイメージ
④ 遷移前のビュー
⑤ 遷移後のビュー
そして、この登場人物でズームの手順を簡単に表現すると次のようになります。
- ⑤「遷移後のビュー」を非表示にする
- ①「ズーム前のイメージ」から②「アニメーション用イメージ」を作成する
- ④「遷移元のビュー」から①「ズーム前のイメージ」だけを非表示にする
- ②「アニメーション用イメージ」を、ズーム前のサイズからズーム後のサイズにアニメーションさせる
- ⑤「遷移後のビュー」を表示する
- ②「アニメーション用イメージ」を削除する
遷移の本体を実装するanimateTransition(using:)のパラメータであるUIViewControllerContextTransitioningからは、遷移前のビューコントローラー及び遷移後のビューコントローラーが取得可能なので、この2つから、上記の登場人物を芋づる式に引っ張り出してきて操作します。
func presentTransition(transitionContext: UIViewControllerContextTransitioning) { // 遷移元ビューコントローラー let firstViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! FirstViewController // 遷移先ビューコントローラー let secondViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! SecondViewController
4 ズームイン
先の手順を実装したものです。ちょっとややこしいのは、セルを取得するところぐらいでしょうか。
// 遷移時のTrastion処理 func presentTransition(transitionContext: UIViewControllerContextTransitioning) { // 遷移元、遷移先及び、遷移コンテナの取得 let firstViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! FirstViewController let secondViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! SecondViewController let containerView = transitionContext.containerView // 遷移元のセルの取得 let cell:CollectionViewCell = firstViewController.collectionView?.cellForItem(at: (firstViewController.collectionView?.indexPathsForSelectedItems?.first)!) as! CollectionViewCell // 遷移元のセルのイメージビューからアニメーション用のビューを作成 let animationView = UIImageView(image: cell.photoView.image) animationView.frame = containerView.convert(cell.photoView.frame, from: cell.photoView.superview) // 遷移元のセルのイメージビューを非表示にする cell.photoView.isHidden = true //遷移後のビューコントローラーを、予め最後の位置まで移動完了させ非表示にする secondViewController.view.frame = transitionContext.finalFrame(for: secondViewController) secondViewController.view.alpha = 0 // 遷移後のイメージは、アニメーションが完了するまで非表示にする secondViewController.photoView.isHidden = true // 遷移コンテナに、遷移後のビューと、アニメーション用のビューを追加する containerView.addSubview(secondViewController.view) containerView.addSubview(animationView) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { // 遷移後のビューを徐々に表示する secondViewController.view.alpha = 1.0 // アニメーション用のビューを、遷移後のイメージの位置までアニメーションする animationView.frame = containerView.convert(secondViewController.photoView.frame, from: secondViewController.view) }, completion: { finished in // 遷移後のイメージを表示する secondViewController.photoView.isHidden = false // セルのイメージの非表示を元に戻す cell.photoView.isHidden = false // アニメーション用のビューを削除する animationView.removeFromSuperview() transitionContext.completeTransition(true) }) }
5 ズームアウト
ズームアウトの処理も、ズームインと殆ど同じです。
// 復帰時のTrastion処理 func dissmissalTransition(transitionContext: UIViewControllerContextTransitioning) { // 遷移元、遷移先及び、遷移コンテナの取得 let secondViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! SecondViewController let firstViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! FirstViewController let containerView = transitionContext.containerView // 遷移元のイメージビューからアニメーション用のビューを作成 let animationView = secondViewController.photoView.snapshotView(afterScreenUpdates: false) animationView?.frame = containerView.convert(secondViewController.photoView.frame, from: secondViewController.photoView.superview) // 遷移元のイメージを非表示にする secondViewController.photoView.isHidden = true // 遷移先のセルを取得 let cell:CollectionViewCell = firstViewController.collectionView?.cellForItem(at: secondViewController.indexPath) as! CollectionViewCell // 遷移先のセルのイメージを非表示 cell.photoView.isHidden = true //遷移後のビューコントローラーを、予め最後の位置まで移動完了させ非表示にする firstViewController.view.frame = transitionContext.finalFrame(for: firstViewController) // 遷移コンテナに、遷移後のビューと、アニメーション用のビューを追加する containerView.insertSubview(firstViewController.view, belowSubview: secondViewController.view) containerView.addSubview(animationView!) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { // 遷移元のビューを徐々に非表示にする secondViewController.view.alpha = 0 // アニメーションビューは、遷移後のイメージの位置まで、アニメーションする animationView?.frame = containerView.convert(cell.photoView.frame, from: cell.photoView.superview) }, completion: { finished in // アニメーション用のビューを削除する animationView?.removeFromSuperview() // 遷移元のイメージの非表示を元に戻す secondViewController.photoView.isHidden = false // セルのイメージの非表示を元に戻す cell.photoView.isHidden = false transitionContext.completeTransition(true) }) }
コードは下記に置きました。気になるところが有りましたら、ぜひ教えてやってください。
[GitHub] https://github.com/furuya02/CustomTransitionSample02
6 UITableViewのズーム
UITableViewの場合も、セルの取得要領以外は、全く同じです。 コードを置きましたので、不明な点があればご参照ください。
[GitHub] https://github.com/furuya02/CustomTransitionSample01
7 最後に
今回は、present modallyの画面遷移で、遷移前の画像が、次の画面にズームしているような表現を書いてみました。
UINavigationControllerや、UITabBarControllerの場合は、遷移の実装方法が変わりますが、ズームを表現する考え方は同じです。 遷移元のビューコントローラと、遷移先のビューコントローラーから、登場人物を捕まてズームさせてやってください。
8 参考リンク
遷移元のビューが透過で見えるモーダルビュー(UIPresentationControllerを使わないで、既存の画面遷移をいじる感じで実装してみました)
iOS7 interactive transitions
UIViewControllerTransitioningDelegate
UIViewControllerAnimatedTransitioning