Home > Mobile >  Restart animation before finish
Restart animation before finish

Time:10-13

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.

  • Related