iOS標準の写真アプリのように画像をズームしながら遷移させる

次のようなiOS標準の写真アプリのように画像をズームする遷移を実装しました。

<img src="https://raw.github.com/wiki/mtj0928/TransitionSample/gif/TransitionSample.gif" width="280">

最初は「よくある遷移だし簡単に実装できるでしょ〜(ﾍﾗﾍﾗ)」って始めたら結構苦労したので、同じような人の手助けになればと思います。(終わってみれば大したことなかった気もする)

正直、[こちら](https://developers.eure.jp/tech/zoom_animation/)を参考にすればそれっぽいものが作れますが、二箇所ほど詰まった点があるので、そちらを解説していきます。
(UIViewControllerAnimatedTransitioningについてわからない場合は[こちら](http://qiita.com/kitoko552/items/4c0e411ff6224090db87)を参考にすると良いと思います)

githubに[サンプル](https://github.com/mtj0928/TransitionSample)を用意したのでそちらもよかったら参考にしてください。

#詰まった点

## 目的のUIViewControllerが取得できない
```func animateTransition(using transitionContext: UIViewControllerContextTransitioning)```内で、遷移元と遷移先のViewControllerを取得する際の

```swift
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
        let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
        
         //以下アニメーションの処理       
}
```

の処理で詰まりました。どう詰まったかというと、UINavigationControllerやUITabbarControllerを使っている場合はそちらが取得されてしまい、遷移の際に必要なUIImageViewを取得できません。
ですので、今回は

```swift
protocol TransitionProtocol {
    func transitionImageView() -> UIImageView //コピーのUIImageViewを返すメソッド
    func trnasitionImageView(isHidden: Bool) //アニメーションの前後でUIImageViewの表示を操作するメソッド
}
```

というプロトコルを用意し、遷移元と遷移先のViewControllerに適用しました。そして、アニメーションの処理を実装しているクラスに次のようなメソッドを実装し、UIImageViewを取得する際には、このメソッドで得られるTransitionProtocolを経由して、取得するようにしました。


```swift
 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を切り替えます。

```swift
import UIKit
import AVFoundation

extension UIImageView {

    var imageSize: CGSize? {
        guard let image = image else { return nil }

        return AVMakeRect(aspectRatio: image.size, insideRect: bounds).size
    }
}
```


```swift
//contentModeを切り替える前後
let center = imageView.center
imageView.frame.size = imageView.imageSize!
imageView.center = center
```
アニメーションの前後などのcontentModeを切り替えるタイミングで上のようにしてやれば、contentModeを変更しても現在表示している状態と変わることはないと思います。
ただし、上のコードは一例でありpresentの時とdismissの時で変わったりもしますので、詳しくは[サンプル](https://github.com/mtj0928/TransitionSample)のコードを見てください。

# さいごに...
何気にQiitaで記事やコードを公開したりするのが初めてだったりします。
普段は独りで開発する機会がほとんどですので、もし「こう書くともっと記事が分かりやすくなる」や「ここのコードはこうするよりもこうした方がいい」などありましたら、アドバイスして頂けますと幸いです。