この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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