I have created a very simple class (SpinningCircleView) of type UIView that performs a spinning circle animation forever. I want to call this class from my ViewController to display the spinning circle animation on the screen. While the animation works great, I am observing incorrect behavior when the app is put in the background. Here is what the spinning circle animation looks like:
To create the spinning circle, I am using two separate UIViewPropertyAnimator, one to rotate the circle by 180 degrees (i.e. Pi) and the other to complete to 360 (i.e. 0 degrees). In the completion block of the second animator, I am then recursively calling the startSpinningCircleAnimation() function. I created a counter to track the number of times the startSpinningCircleAnimation() is called (i.e. the number of times the circle has rotated). While the app is active (in the foreground), the counter increments as expected and I can see the output in my Xcode terminal window:
Starting animation
1: + START Spinning Circle Animation
2: + START Spinning Circle Animation -> recursive call
3: + START Spinning Circle Animation -> recursive call
4: + START Spinning Circle Animation -> recursive call
The problem or incorrect behavior happens is when I put the app into the background...all of a sudden I am seeing several hundred "+ START Spinning Circle Animation -> recursive call" in my terminal. When the app comes back to the foreground, the counter and terminal output resume normal increments of the counter.
Why are several hundred calls being made to the startSpinningCircleAnimation() function when the app is put in the background? How can I correctly pause the animation and resume the animation as the app is moved between background and foreground? I have scoured through various posts but I can't figure out the solution.
Please help!!
Here is my SpinningCircleView class:
import UIKit
class SpinningCircleView: UIView
{
private lazy var spinningCircle = CAShapeLayer()
private lazy var animator1 = UIViewPropertyAnimator(duration: 1, curve: .linear, animations: nil)
private lazy var animator2 = UIViewPropertyAnimator(duration: 1, curve: .linear, animations: nil)
public var counter = 0
override init (frame: CGRect)
{
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
private func configure()
{
frame = CGRect(x: 0, y: 0, width: 100, height: 100)
let rect = self.bounds
let circularPath = UIBezierPath(ovalIn: rect)
spinningCircle.path = circularPath.cgPath
spinningCircle.fillColor = UIColor.clear.cgColor
spinningCircle.strokeColor = UIColor.systemRed.cgColor
spinningCircle.lineWidth = 10
spinningCircle.strokeEnd = 0.25
spinningCircle.lineCap = .round
self.layer.addSublayer(spinningCircle)
}
func startSpinningCircleAnimation()
{
counter += 1
let criteria1 = animator1.state == .active && !animator1.isRunning
let criteria2 = animator2.state == .active && !animator2.isRunning
let criteria3 = animator1.state == .inactive && animator2.state == .inactive
let criteria4 = (animator1.state == .inactive && animator2.state.rawValue == 5) || (animator2.state == .inactive && animator1.state.rawValue == 5)
if (criteria1)
{
// Since animator1 is Paused, we will resume the animation
print("\(self.counter): ~ RESUME Spinning Circle Animation")
animator1.startAnimation()
} else if (criteria2)
{
// Since animator2 is Paused, we will resume the animation
print("\(self.counter): ~ RESUME Spinning Circle Animation")
animator2.startAnimation()
} else if (criteria3 || criteria4)
{
if (criteria3)
{
print("\(self.counter): + START Spinning Circle Animation")
} else if (criteria4)
{
print("\(self.counter): + START Spinning Circle Animation -> recursive call")
}
animator1.addAnimations
{
self.transform = CGAffineTransform(rotationAngle: .pi)
}
animator1.addCompletion
{ _ in
self.animator2.addAnimations
{
self.transform = CGAffineTransform(rotationAngle: 0)
}
self.animator2.addCompletion
{ _ in
// Recursively call this start spinning
self.startSpinningCircleAnimation()
}
self.animator2.startAnimation()
}
animator1.startAnimation()
} else
{
print("\(self.counter): >>>>>>>>> HERE <<<<<<<<<<< \(self.animator1.state) \(self.animator1.isRunning) \(self.animator2.state) \(self.animator2.isRunning)")
}
}
func stopSpinningCircleAnimation()
{
print("\(self.counter): - STOP Spinning Circle Animation Begin: \(self.animator1.state) \(self.animator1.isRunning) \(self.animator2.state) \(self.animator2.isRunning)")
if (self.animator1.isRunning)
{
self.animator1.pauseAnimation()
} else if (self.animator2.isRunning)
{
self.animator2.pauseAnimation()
}
print("\(self.counter): - STOP Spinning Circle Animation End: \(self.animator1.state) \(self.animator1.isRunning) \(self.animator2.state) \(self.animator2.isRunning)")
}
}
Here is my ViewController which sets up an instance of the SpinningCircleView and starts animating:
class ViewController: UIViewController
{
private lazy var spinningCircleView = SpinningCircleView()
override func viewDidLoad()
{
super.viewDidLoad()
// Setup the spinning circle and display the animation to the screen
spinningCircleView.frame = CGRect(x: view.center.x - 50, y: 100, width: 100, height: 100)
spinningCircleView.tag = 100
view.addSubview(spinningCircleView)
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
print("Starting animation")
spinningCircleView.startSpinningCircleAnimation()
}
override func viewDidDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
print("Pausing animation")
spinningCircleView.stopSpinningCircleAnimation()
}
}