クリックできる目次
はじめに
こんにちは。個人のアプリ開発をしていて、UIViewPropertyAnimatorを使ってアニメーションのリピートをさせたくなりました。
しかし、iOS13.0でrepeat指定など、使えなくなった書き方が多々あり、実装に詰まったので書き残したいと思います。参考になれば幸いです。
iOS13でのリピート動作の問題について
setAnimationRepeatCountのdeprecated
iOS13前では、アニメーションのリピートの回数をanimations
ブロックで記述することで指定できる設定メソッド、setAnimationRepeatCount:がありました。
しかし現在は利用が非推奨のようなので、この方法は使えませんでした。
UIView.AnimationOptionsの.repeatが機能しない
iOS13以降の現象?なのか、runningPropertyAnimator(withDuration:delay:options:animations:completion:)メソッドで指定できるoptions
に[.curveLinear, .repeat]
等を設定しても、無視されるようで動作しませんでした。
解決方法
この他にTimer
クラスを利用する方法がありましたが、アニメーション時間の管理が大変そうだったので、今回はアニメーションのaddCompletion(_:)
内であらかじめ関数で用意しておいた自分自身を呼ぶ再帰的な処理(少し不安ですが…)でリピート処理を行いました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import UIKit class SampleViewController: UIViewController { var sampleView: UIView! var sampleAnimator: UIViewPropertyAnimator! let screenSize = UIScreen.main.fixedCoordinateSpace.bounds.size let sampleViewSize = CGSize(width: 100, height: 100) var startMoveRepeatAnimation: (() -> ())? override func viewDidLoad() { super.viewDidLoad() self.startMoveRepeatAnimation = { // アニメーション前の状態にプロパティを戻しておく self.sampleView.frame.origin.x = 0 // Animatorの作成 self.sampleAnimator = UIViewPropertyAnimator(duration: 2.0, curve: .linear, animations: { // 右に移動するようなアニメーション self.sampleView.frame.origin.x = self.screenSize.width - self.sampleViewSize.width }) // Animatorへ完了処理の登録 self.sampleAnimator.addCompletion { position in // アニメーションが終了した時 if position == .end { print("sampleAnimator Completion") // 完了時に再度アニメーションを呼ぶ self.startMoveRepeatAnimation?() } } // アニメーションの開始 self.sampleAnimator.startAnimation() } // Viewの作成 self.sampleView = UIView(frame: CGRect(origin: CGPoint(x: 0, y: 100), size: sampleViewSize)) self.sampleView.backgroundColor = UIColor.systemIndigo self.view.addSubview(sampleView) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) // ここでちゃんとnilを入れないと、画面を閉じた後なども永久に呼ばれてしまう self.startMoveRepeatAnimation = nil } /// ボタンが押された時の処理 @IBAction func buttonAction(_ sender: UIBarButtonItem) { // アニメーションの開始 self.startMoveRepeatAnimation?() } } |
UIViewPropertyAnimatorの注意点など
Animatorの再利用について
私は、UIViewPropertyAnimator
は先にアニメーションと完了処理を登録するので、アニメーション前の状態にプロパティを戻して、
再度self.animator.startAnimation()
を呼び出せば同じアニメーションが呼ばれると勘違いしました。(恥ずかしい)
実際は、Timerの再利用ができないように、Animatorも再利用ができないようです。同じアニメーションを実行するにはコンストラクタで再度インスタンスを生成する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Animatorの再利用はできないので、再度生成しなおす self.sampleAnimator = UIViewPropertyAnimator(duration: 2.0, curve: .linear, animations: { // 右に移動するようなアニメーション self.sampleView.frame.origin.x = self.screenSize.width - self.sampleViewSize.width }) // ..... /// ボタンが押された時の処理 @IBAction func buttonAction(_ sender: UIBarButtonItem) { // アニメーション前にプロパティを戻しても2回目は呼べない self.sampleView.frame.origin.x = 0 self.sampleAnimator.startAnimation() } |
アニメーションの遅延実行について
UIViewPropertyAnimator
やUIView.animate
にて、アニメーションをaddCompletion(_:)
でつなげて実行していくような際に、以下のようなanimations
ブロックで、何も処理しないアニメーションを作成すれば、指定秒数待機するようにできると勘違いしました。実際は、アニメーションはduration
もdelay
の値は適用されず、すぐにアニメーションの完了が呼ばれます。
1 2 3 4 5 6 7 8 9 10 11 |
let waitAnimator = UIViewPropertyAnimator(duration: 2.0, curve: .linear, animations: {}) waitAnimator.addCompletion { position in if position == .end { print("Completion!") } } waitAnimator.startAnimation() // Completion!がすぐに呼ばれてしまう |
そもそも、UIView
のアニメーションでは、animations
ブロックにアニメーション対象となるプロパティの変更を行わないといけないようでした。(恥ずかしい)
なので少し違和感がありますが、以下のようにasyncAfter
を利用するしかないようでした。
1 2 3 4 |
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: { // 5.0秒後に以下アニメーションを行う propertyAnimator.startAnimation() }) |
さいごに
見ていただいてありがとうございます。何か他に知見があればぜひコメントを頂けたら嬉しいです。