YouTubeアプリ風の画面の向き変更時のアニメーションを実装してみる


![20160315.gif](https://qiita-image-store.s3.amazonaws.com/0/40693/7d21c8b1-8e13-c452-c27f-e4e3fc038c83.gif "20160315.gif")


YouTubeの公式アプリの動画のレイヤー？は横向きにした場合、全画面に広がりますがそのタイミングがデフォルトの遷移のタイミングより少し遅いのが気になって再現出来るか試してみました。
結論としては似たような動きが出来たのでとりあえずこれでいいかなあと。（ちょっと上記のgifでは分かりづらいけど）



新しいプロジェクトでSingleViewApplicationを選択してStoryBoard上のViewControllerにUIViewとTableViewをそれっぽく配置してAutoLayoutで位置とサイズを指定

![スクリーンショット 2016-03-16 00.06.24.png](https://qiita-image-store.s3.amazonaws.com/0/40693/a8dc46f2-a774-264d-9af7-acee653c26bd.png "スクリーンショット 2016-03-16 00.06.24.png")

TableViewの実装は今回パスして、とりあえず動画の部分だけを実装。

UIViewとそれのAutoLayoutをViewController.swiftに紐付けておく

一つ断っておくと今回、LandScapeからPortraitからと、その逆の操作時の動画ビューの大きさを変更するアニメーションの実装方法が違います。
先方はAutoLayoutを利用、もう片方は昔ながらのframeのサイズを変更する方法。
統一した方がいいのはもちろんなのですが、あくまでお試しという事でご容赦。
（AutoLayoutを使ってPortraitからLandScape時のAutoLayoutの設定が面倒そうだったので挫折しただけですが）


```swift:ViewController

    @IBOutlet weak var topConstraint: NSLayoutConstraint!
    @IBOutlet weak var leftConstraint: NSLayoutConstraint!
    @IBOutlet weak var rightConstraint: NSLayoutConstraint!
    
    @IBOutlet weak var subView: UIView!

    //サポートするデバイスの向きを指定する
    override func supportedInterfaceOrientations()  -> UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.Portrait
    }
    
    var player = AVPlayer()
    var playerLayer: AVPlayerLayer!
    var invisibleButton = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        
        if let url = NSBundle.mainBundle().URLForResource("IMG_0727", withExtension: "mp4") {
            do {
                player = AVPlayer(URL: url)
                playerLayer = AVPlayerLayer(player: player)
                subView.layer.insertSublayer(playerLayer, atIndex: 0)
                
                self.playerLayer.anchorPoint = CGPointZero
                self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill

                //再生・停止ボタン追加
                subView.addSubview(invisibleButton)
                invisibleButton.addTarget(self, action: "invisibleButtonTapped:",
                    forControlEvents: UIControlEvents.TouchUpInside)

                
                //最前面にしたい
                self.view.bringSubviewToFront(subView)
            }
        } else {
            fatalError("error")
        }
        
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // 端末の向きがかわったら呼び出される.
    func onOrientationChange(notification: NSNotification){
        
        // 向き毎に制御
        if(UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation)){

            UIView.animateWithDuration(1.0, delay: 0.5, options: UIViewAnimationOptions.CurveEaseIn, animations: { () -> Void in
                
                if(UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeLeft) {
                    self.subView.transform = CGAffineTransformMakeRotation((CGFloat(90.0 * M_PI) / 180.0))
                } else {
                    self.subView.transform = CGAffineTransformMakeRotation((CGFloat(270.0 * M_PI) / 180.0))
                }
                self.subView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)
                
            }, completion: { (Bool) -> Void in
            })

            let oldBounds = self.playerLayer.bounds
            var newBounds = oldBounds
            newBounds.size = CGSizeMake(self.view.frame.size.height, self.view.frame.size.width)
            
            let animation : CABasicAnimation = CABasicAnimation(keyPath: "bounds")
            animation.delegate = self
            animation.beginTime = CACurrentMediaTime() + 0.5
            animation.fromValue = NSValue(CGRect: oldBounds)
            animation.toValue =  NSValue(CGRect: newBounds)
            animation.autoreverses = false
            animation.removedOnCompletion = false
            animation.fillMode = kCAFillModeForwards
            animation.duration = 1.0
            
            self.playerLayer.addAnimation(animation, forKey: "rotate-landscape")
            
        } else if(UIDeviceOrientationIsPortrait(UIDevice.currentDevice().orientation)) {

            self.view.setNeedsUpdateConstraints()
            
            self.topConstraint.constant = 0
            self.rightConstraint.constant = 54
            self.leftConstraint.constant = 52

            UIView.animateWithDuration(1.0, delay: 0.5, options: UIViewAnimationOptions.CurveEaseIn, animations: { () -> Void in
                self.subView.transform = CGAffineTransformIdentity
                
                self.view.layoutIfNeeded()


                }, completion: { (Bool) -> Void in

            })

            let oldBounds = self.playerLayer.bounds
            var newBounds = oldBounds
            newBounds.size = CGSizeMake(self.subView.frame.size.width, self.subView.frame.size.height)
            
            let animation : CABasicAnimation = CABasicAnimation(keyPath: "bounds")
            animation.delegate = self
            animation.beginTime = CACurrentMediaTime() + 0.5
            animation.fromValue = NSValue(CGRect: oldBounds)
            animation.toValue =  NSValue(CGRect: newBounds)
            animation.autoreverses = false
            animation.removedOnCompletion = false
            animation.fillMode = kCAFillModeForwards
            animation.duration = 1.0

            self.playerLayer.addAnimation(animation, forKey: "rotate-portant")
        }
        
    override func viewDidAppear(animated: Bool) {
        UIView.performWithoutAnimation { () -> Void in
            self.playerLayer.frame = self.subView.bounds
        }
        
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "onOrientationChange:", name: UIDeviceOrientationDidChangeNotification, object: nil)

    }
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        invisibleButton.frame = subView.bounds
    }
    
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        
        if(anim == self.playerLayer.animationForKey("rotate-landscape")){
            self.playerLayer.bounds.size = CGSizeMake(self.view.frame.size.height, self.view.frame.size.width)
            self.playerLayer.removeAnimationForKey("rotate-landscape")
        }else if(anim == self.playerLayer.animationForKey("rotate-portant")){
            self.playerLayer.bounds.size = CGSizeMake(self.subView.frame.size.width, self.subView.frame.size.height)
            self.playerLayer.removeAnimationForKey("rotate-portant")
            self.playerLayer.removeAllAnimations()
            
        }
        invisibleButton.frame = self.playerLayer.bounds
   
    }
    
    func invisibleButtonTapped(sender: UIButton!) {
        let playerIsPlaying:Bool = player.rate > 0
        if (playerIsPlaying) {
            player.pause();
        } else {
            player.play();
        }
    }
    
}
    
```

追加したCALayerは、UIViewの大きさの変更に連動しないようなのでUIViewのアニメーションと同じタイミングで動くようにCALayer用のアニメーションも動かしてやる必要があります。


###参考



[A Custom Video Player for iOS with AVFoundation]
(http://binarymosaic.com/custom-video-player-for-ios-with-avfoundation/)

AVPlayerLayerの実装と再生、停止ボタンの実装方法はこちらをそのまま参考。英語だけど分かりやすい！


[【Objective-C】iOS8になり、動画の縦横回転の実装が難しくなった話](http://ttr-hamasaki.hatenablog.com/entry/2014/09/23/004716)

方向変換時のアニメーションで大変参考になりました。