この記事はQiitaに投稿されたものの転載です。


やたいこと

  • ジェスチャに連動させて画面遷移させたい
    • UIViewControllerAnimatedTransitioning, UIPercentDrivenInteractiveTransition 使用
  • 画面遷移のアニメーションは、1つのアニメーションが終わってから別のアニメーションを実行させたい

UIViewControllerAnimatedTransitioningとUIPercentDrivenInteractiveTransitionの実装サンプルは UIVIew.animate を1回使って1つのアニメーションをさせるものばかりで意図した動きにならなかったため、他の実装を探しました。

試したこと1: UIVIew.animateのcompletionで次のアニメーション実行

コード

            let duration = transitionDuration(using: transitionContext)
            UIView.animate(withDuration: duration / 2, delay: 0, animations: {
                toViewController.view.alpha = 0.1
            }) { (finished) in
                UIView.animate(withDuration: duration / 2, delay: 0, options: [], animations: {
                    toViewController.view.alpha = 1
                }) { (finished) in
                }
            }

結果

画面遷移には1つ目のアニメーションが使われ、画面遷移が終わってから2つめのアニメーションが開始

試したこと2: UIView.animateのdelayで2つめのアニメーションを遅らせる

コード

            let duration = transitionDuration(using: transitionContext)
            UIView.animate(withDuration: duration / 2, delay: 0, animations: {
                toViewController.view.alpha = 0.1
            }) { (finished) in
            }

            UIView.animate(withDuration: duration / 2, delay: duration / 2, options: [], animations: {
                    toViewController.view.alpha = 1
            }) { (finished) in
            }

結果

画面遷移開始時は1つめのアニメーションが終わった状態になっており、画面遷移のアニメーションには2つめのアニメーションが使われる

解決策

@kishikawakatsumi さんによるスタック・オーバーフローの回答で、アニメーションを連続指定できる UIView.animateKeyframes を知りました。

https://ja.stackoverflow.com/questions/38571

これを使うことで思ったとおりの動きになりました。

            UIView.animateKeyframes(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [], animations: {
                UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
                    toViewController.view.alpha = 0.1
                }
                UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
                    toViewController.view.alpha = 1
                }
            })

注意点

ジェスチャの途中でUIPercentDrivenInteractiveTransitionの finish() を呼んでもアニメーションが連続で実行されずに最終的な状態へ直接アニメーションしました。

これは update(_:) を小刻みに呼ぶことで解決しました。 ただ DispatchQueue.main.asyncAfter であらかじめすべてのupdateをスケジュールすると正確な時間に実行されないのか動きがカクカクになったため、1回updateを行ったら次をスケジュールして実行、という実装に変えたらうまくいきました。

なぜか UIPercentDrivenInteractiveTransition.completionCurve を指定することで解決しました。

環境

Swift 4.2 iOS 12.1