Swift アニメーション実装
最近、ちゃんとアニメーション周りを勉強しないとダメだなぁと思っているにっきーです。
UIKitで提供されているUIView.animate
はよく使うのですが、その他のアニメーション実装でいろいろ調べてみると便利だなと思ったものが幾つかあったので、ほんの一部ですがまとめてみました。
キーフレームアニメーション
キーフレーム追加で複数アニメーションを組み合わせる
UIView.animateKeyframesを使うと、複数のアニメーションを組みわせることができます。
CAAnimationGroupでも同じことができるのですが、ちょっとしたアニメーションの組み合わせであれば、UIView.animateKeyframes
を使ったほうがお手軽にできてよさそうです。
例)移動しながら途中から回転してフェードアウト
UIView.animateKeyframes(withDuration: 2.0, delay: 0.0, animations: { UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1.0, animations: { self.imageView.center.y += 300.0 }) UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { self.imageView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2)) }) UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { self.imageView.alpha = 0.0 }) }, completion: nil)
UIView.addKeyframe
で指定している値ですが、UIView.animateKeyframes
のwithDuration
で指定したアニメーション全体の長さに対して、相対値で指定するのがポイントです。
- withRelativeStartTime:何秒後にアニメーションが開始されるかを0〜1の範囲で指定します。アニメーションの全体の長さが2秒の場合、0.5を指定するとアニメーション全体が開始してから1秒後にアニメーションが開始されます。
- relativeDuration:この値も0〜1の範囲で指定します。アニメーションの全体の長さが2秒の場合、0.5を指定すると、アニメーションの長さは1秒になります。
UIView.animateのcompletionのネストを解消
また、よくあるケースとしてUIView.animate
でアニメーション完了後のcompletionで次のアニメーションを実行させたいことがありますが、アニメーションの順次実行は複数になるとcompletion内のブロックがネストするので、見通しがわるくなるかと思います。
そういった場合にもUIView.animateKeyframes
を使うと、もう少しコードがスッキリするかと思います。
(UIView.animateのcompletionでネストする場合とUIView.animateKeyframesを利用するのとでアニメーションの滑らかさも違いました。)
例)UIView.animateのcompletionでネスト
UIView.animate(withDuration: 1.0, delay: 0.0, animations: { self.imageView.center.y += 300.0 }) { _ in UIView.animate(withDuration: 1.0, delay: 0.0, animations: { self.imageView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2)) }, completion: nil) }
例)UIView.animateKeyframesでアニメーションの順次実行
UIView.animateKeyframes(withDuration: 2.0, delay: 0.0, options: [.autoreverse], animations: { UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: { self.blueView.center.y += 300.0 }) UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 1.0, animations: { self.blueView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2)) }) }, completion: nil)
配列による指定でスッキリ
キーフレームアニメーションは変更対象の値が1つの場合などは、Core AnimationのCAKeyframeAnimationを利用すると、配列で値を設定できるのでコードの見通しが良くなり、スッキリします。
例)CAKeyframeAnimationで配列による指定
let colorKeyframeAnimation = CAKeyframeAnimation(keyPath: "backgroundColor") colorKeyframeAnimation.values = [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor] colorKeyframeAnimation.keyTimes = [0, 0.5, 1.0] colorKeyframeAnimation.duration = 2.0 view.layer.add(colorKeyframeAnimation, forKey: nil)
keyTimesは変更値を適用する時間をdurationに対する相対値で指定します。
スプリングアニメーション
よく見る弾むようなスプリングアニメーションですが、UIKitで提供されているものでもできますが、細かい指定ができませんでした。iOS9から利用できるようになったCASpringAnimationを使えば、もっと細かい指定ができます。
例)UIKit提供のスプリングアニメーション
UIView.animate( withDuration: 3.0, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, animations: { self.imageView.frame.origin.y += 100.0 }, completion: nil )
例)CASpringAnimation
let spring = CASpringAnimation() spring.keyPath = "position.y" spring.fromValue = 100.0 spring.toValue = 200.0 spring.damping = 5.0 spring.mass = 2.0 spring.damping = 3.0 spring.stiffness = 100.0 spring.duration = spring.settlingDuration imageView.layer.add(spring, forKey: nil)
CASpringAnimationでは以下の値が指定が可能なので、細かい調整ができます。
- mass(質量:この値が大きいと振り子が振れる時間が長くなる)
- stiffness(振り子の弾性力:この値が大きいとすぐに振り子の振幅が小さくなる)
なお、settlingDurationはreadonlyプロパティで、振り子の動きが終わるまでに必要な時間です。
そのため、アニメーション時間のdurationにセットしておくといいと思います。(settlingDurationをdurationにセットせず、durationがsettlingDurationより短い時間の場合は、スプリング中にアニメーションが終了していまします。)
CATransaction
Core Animationの終了を検知して何かしらの処理を行う場合に、CAAnimationDelegateで通知を受け取って処理することも可能ですが、CATransactionを利用するとsetCompletionBlock
でアニメーション終了時の処理をブロックで渡せるので、ソースコードの見通しがよくなります。
例)CAAnimationDelegateを利用
let animation = CABasicAnimation(keyPath: "transform.scale") animation.delegate = self animation.duration = 0.3 animation.fromValue = 1.0 animation.toValue = 2.0 imageView.layer.add(animation, forKey: nil) extension SomeAnimationViewController: CAAnimationDelegate { func animationDidStart(_ anim: CAAnimation) { //開始時の処理を記述 } func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { // 終了時の処理を記述 } }
例)CATransactionのsetCompletionBlockを利用
CATransaction.begin() CATransaction.setCompletionBlock { // 終了時の処理を記述 } let animation = CABasicAnimation(keyPath: "transform.scale") animation.duration = 0.3 animation.fromValue = 1.0 animation.toValue = 2.0 imageView.layer.add(animation, forKey: nil) CATransaction.commit()
UIViewPropertyAnimator
iOS10から使えるようになりました。
サンプルコードは以下の通りです。
例)UIViewPropertyAnimator
class SomeAnimationViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! var animator: UIViewPropertyAnimator! override func viewDidLoad(){ super.viewDidLoad() animator = UIViewPropertyAnimator(duration: 3.0, curve: .linear){ self.imageView.center.y += 300 } } @IBAction func buttonTapped(_ sender: UIButton) { // ボタンタップでアニメーション開始 animator.startAnimation() }
特徴はアニメーションの開始、一時停止、終了が簡単にできることです。
UIViewPropertyAnimatorはたどるとUIViewAnimatingプロトコルを採用しているUIViewImplicitlyAnimatingプロトコルを採用しており、UIViewAnimatingで定義されているアニメーション開始、一時停止、終了のメソッドが定義されています。
また、アニメーションの状態(inactive、active、stopped)やアニメーションの進捗状況を0〜1で取得可能です。アニメーションの進捗状況fractionComplete
はセットすることも可能なので、他の何かの値に合わせて進捗状況をコントロールすることもできます。
self.animator?.fractionComplete = value
なお、途中でのアニメーション追加も簡単にできます。
animator.addAnimations { self.imageView.alpha = 0.0 } animator.startAnimation()
もちろん、アニメーション終了時の処理も追加できます。
animator.addCompletion {_ in // 終了後からの変化する処理を記述 }
UIViewPropertyAnimatorはアニメーション途中でユーザーからの何らかのアクションなどに応じで変化するような動的なアニメーションが簡単に実装できそうです。
最後に
UI作成ではアニメーションで動きを入れるなどアニメーションを実装する機会は多いですが、なるべく煩雑にならないようにアニメーションの実装を考えていきたいと思います。
TAG
最近、仕事に復帰し、子育てと仕事の両立に奮闘しているママエンジニアです。趣味はダイビングで、海に潜って魚や珊瑚の写真を撮るのが好きです。
TAG
- Android
- AWS
- Bitrise
- CodePipeline
- Firebase
- HTML
- iOS
- IoT
- JavaScript
- KPI
- Linux
- Mac
- Memcached
- MGRe
- MGReのゆるガチエンジニアブログ
- MySQL
- PHP
- PICK UP
- PR
- Python
- Ruby
- Ruby on Rails
- SEO
- Swift
- TIPS
- UI/UX
- VirtualBox
- Wantedly
- Windows
- アクセス解析
- イベントレポート
- エンジニアブログ
- ガジェット
- カスタマーサクセス
- サーバ技術
- サービス
- セキュリティ
- セミナー・展示会
- テクノロジー
- デザイン
- プレスリリース
- マーケティング施策
- マネジメント
- ラボ
- リーンスタートアップ
- 企画
- 会社紹介
- 会社紹介資料
- 勉強会
- 実績紹介
- 拡張性
- 採用
- 日常
- 書籍紹介
- 歓迎会
- 社内イベント
- 社員インタビュー
- 社長ブログ
- 視察
- 開発環境