Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

20160315.gif

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

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

スクリーンショット 2016-03-16 00.06.24.png

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

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

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

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

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

【Objective-C】iOS8になり、動画の縦横回転の実装が難しくなった話

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

rnsm504
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした