Search code examples
iosswiftcabasicanimation

How to restart a CABasicAnimation after it is completed


I have a button and when pressed it runs a CABasicAnimation in 2 seconds. The first time I press the button the animation runs fine and the layer is filled red. The second time I press the button the animation doesn't run again. The red layer disappears because I remove it but it doesn't start back from the beginning again.

In the code below the basicAnimation is a class property. I also tried creating it inside the buttonPressed method itself which made no difference and then I tried adding it as an optional var basicAnimation: CABasicAnimation?, instantiating it inside the buttonPressed method, then setting it to nil inside the animationDidStop(_:finished:) delegate method but neither worked.

Why doesn't is run again?

let shapeLayer = CAShapeLayer()

let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
// var basicAnimation: CABasicAnimation? // I tried this

override func viewDidLoad() {
    super.viewDidLoad()

    let circularPath = UIBezierPath(arcCenter: view.center, radius: 100, startAngle: -.pi/2, endAngle: 3 * .pi/2, clockwise: true)
    shapeLayer.path = circularPath.cgPath
    shapeLayer.strokeColor = UIColor.red.cgColor
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.lineWidth = 10
    shapeLayer.strokeEnd = 0
    view.layer.addSublayer(shapeLayer)
}

@objc func buttonTapped() {

    // basicAnimation = CABasicAnimation(keyPath: "strokeEnd") // I tried this
    // let basicAnimation = CABasicAnimation(keyPath: "strokeEnd") // I also tried this
    removeAnimation()

    basicAnimation.delegate = self
    basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
    basicAnimation.fromValue = 0
    basicAnimation.toValue = 1
    basicAnimation.duration = CFTimeInterval(2)
        
    basicAnimation.fillMode = CAMediaTimingFillMode.forwards
    basicAnimation.isRemovedOnCompletion = false
        
    shapeLayer.add(basicAnimation, forKey: "myAnimation")
}

func removeAnimation() {
    shapeLayer.removeAnimation(forKey: "myAnimation")
}

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    print("animation done") // prints once

    // basicAnimation = nil // I tried this
}

Solution

  • I was able to make your code work just by making a new animation each time the button is tapped:

    let shapeLayer = CAShapeLayer()
    
    var basicAnimation: CABasicAnimation!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let circularPath = UIBezierPath(arcCenter: view.center, radius: 100, startAngle: -.pi/2, endAngle: 3 * .pi/2, clockwise: true)
        shapeLayer.path = circularPath.cgPath
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.strokeEnd = 0
        view.layer.addSublayer(shapeLayer)
    }
    
    @IBAction func buttonTapped(_ sender: Any) {
    
        basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        removeAnimation()
    
        basicAnimation.delegate = self
        basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
        basicAnimation.fromValue = 0
        basicAnimation.toValue = 1
        basicAnimation.duration = CFTimeInterval(2)
            
        basicAnimation.fillMode = CAMediaTimingFillMode.forwards
        basicAnimation.isRemovedOnCompletion = false
            
        shapeLayer.add(basicAnimation, forKey: "myAnimation")
    }
    
    func removeAnimation() {
        shapeLayer.removeAnimation(forKey: "myAnimation")
    }
    
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        print("animation done")
    }
    

    enter image description here