次のようなiOS標準の写真アプリのように画像をズームする遷移を実装しました。
最初は「よくある遷移だし簡単に実装できるでしょ〜(ヘラヘラ)」って始めたら結構苦労したので、同じような人の手助けになればと思います。(終わってみれば大したことなかった気もする)
正直、こちらを参考にすればそれっぽいものが作れますが、二箇所ほど詰まった点があるので、そちらを解説していきます。
(UIViewControllerAnimatedTransitioningについてわからない場合はこちらを参考にすると良いと思います)
githubにサンプルを用意したのでそちらもよかったら参考にしてください。
詰まった点
目的のUIViewControllerが取得できない
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
内で、遷移元と遷移先のViewControllerを取得する際の
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
//以下アニメーションの処理
}
の処理で詰まりました。どう詰まったかというと、UINavigationControllerやUITabbarControllerを使っている場合はそちらが取得されてしまい、遷移の際に必要なUIImageViewを取得できません。
ですので、今回は
protocol TransitionProtocol {
func transitionImageView() -> UIImageView //コピーのUIImageViewを返すメソッド
func trnasitionImageView(isHidden: Bool) //アニメーションの前後でUIImageViewの表示を操作するメソッド
}
というプロトコルを用意し、遷移元と遷移先のViewControllerに適用しました。そして、アニメーションの処理を実装しているクラスに次のようなメソッドを実装し、UIImageViewを取得する際には、このメソッドで得られるTransitionProtocolを経由して、取得するようにしました。
private func getTransitionProtocol(viewController: UIViewController?) -> TransitionProtocol? {
guard var viewController = viewController else { return nil }
while (viewController as? TransitionProtocol) == nil {
if let tab = viewController as? UITabBarController {
viewController = tab.selectedViewController!
} else if let navi = viewController as? UINavigationController {
let viewControllers = navi.childViewControllers
viewController = navi.childViewControllers[viewControllers.count - 1]
} else {
return nil
}
}
return viewController as? TransitionProtocol
}
contentModeの切り替え
しかし、このままだとアニメーションの前後で画像が少しカクッとしてしまいます。
これの原因としてはUIImageViewのframeがそのままであるにも関わらず、アニメーションの前後でcontentModeが切り替わってしまうためです。
そのため、今回は解決策としてUImageViewに表示しているUIImageの大きさを取得できるようUIImageViewをextensionし、画像の大きさを変えることなくcontentModeを切り替えます。
import UIKit
import AVFoundation
extension UIImageView {
var imageSize: CGSize? {
guard let image = image else { return nil }
return AVMakeRect(aspectRatio: image.size, insideRect: bounds).size
}
}
//contentModeを切り替える前後
let center = imageView.center
imageView.frame.size = imageView.imageSize!
imageView.center = center
アニメーションの前後などのcontentModeを切り替えるタイミングで上のようにしてやれば、contentModeを変更しても現在表示している状態と変わることはないと思います。
ただし、上のコードは一例でありpresentの時とdismissの時で変わったりもしますので、詳しくはサンプルのコードを見てください。
さいごに...
何気にQiitaで記事やコードを公開したりするのが初めてだったりします。
普段は独りで開発する機会がほとんどですので、もし「こう書くともっと記事が分かりやすくなる」や「ここのコードはこうするよりもこうした方がいい」などありましたら、アドバイスして頂けますと幸いです。