I have a UIViewController with Circular Progress Bar View and restart button. The problem is that when I try to restart animation I can't add it again.
In my restartButton( _ sender: UIButton) function I use "progressLayer.removeAllAnimations()" and my stroke totally dissapear (and it's ok because I remove animation), but after that I use "addAnimation" function which should add the animation again but it doesn't happen (the stroke doesn't start again).
And what is surprising in viewDidAppear addAnimation() function works properly but in restartButton it doesn't, like it could not add animation again after using removeAllAnimations()
It's my view with circular progress bar
@IBDesignable
class CircularProgressBar: UIView {
private var progressLayer = CAShapeLayer()
private var backgroundMask = CAShapeLayer()
@IBInspectable
var ringWidth: CGFloat = 15
@IBInspectable
var color: UIColor? = .purple {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupLayers()
}
private func setupLayers() {
backgroundMask.lineWidth = ringWidth
backgroundMask.fillColor = nil
backgroundMask.strokeColor = UIColor.gray.cgColor
layer.addSublayer(backgroundMask)
progressLayer.lineWidth = ringWidth
progressLayer.fillColor = nil
layer.addSublayer(progressLayer)
}
override func draw(_ rect: CGRect) {
let circlePath = UIBezierPath(arcCenter: self.center, radius: 140, startAngle: -(.pi / 2), endAngle: (3 * .pi / 2), clockwise: true).cgPath
backgroundMask.path = circlePath
progressLayer.path = circlePath
progressLayer.strokeEnd = 0
progressLayer.strokeColor = UIColor.yellow.cgColor
}
func addAnimation() {
let duration: TimeInterval = 10
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1
animation.duration = duration
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
progressLayer.add(animation, forKey: nil)
}
func restartCircularProgressBar() {
progressLayer.removeAllAnimations()
}
}
And it's my UIViewController
class AnimationViewController: UIViewController {
@IBOutlet weak var circularProgressBar: CircularProgressBar!
override func viewDidAppear(_ animated: Bool) {
circularProgressBar.addAnimation()
}
@IBAction func restartButton(_ sender: UIButton) {
circularProgressBar.restartCircularProgressBar()
circularProgressBar.addAnimation()
}
}
The problem appears only when I try to restart animation but I don't know why, because I use removeAnimation(), but after that I add animation again.
CodePudding user response:
It's because you're doing the animation incorrectly. These lines are totally wrong:
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
Never say that kind of thing. I know there are a lot of examples floating around out there that do it, but don't copy them.
Delete those two lines, and put in their place:
CATransaction.setDisableActions(true)
progressLayer.strokeEnd = 1
That will solve part of the problem you've pointed to: if you wait for the animation to complete and then hit the button that triggers the restart of the animation, it will work.
However, that still won't cause the animation to restart if you hit the button while the animation is taking place. For that, rewrite your restart
method like this:
func restartCircularProgressBar() {
progressLayer.removeAllAnimations()
CATransaction.setDisableActions(true)
progressLayer.strokeEnd = 0
CATransaction.flush()
}
Do you see the theme here? In both cases, the issue is that you're failing to grapple with the question of what the model layer's strokeEnd
value should in fact be. Animation is just an illusion; you have to configure the reality too. To animate to 1, set the model layer strokeEnd
to 1 and animate. To reset, set the model layer strokeEnd
to 0.