Search code examples
swiftcore-animationcakeyframeanimation

How do I set an endpoint for a CAKeyFrameAnimation?


I am using an arc for a progress bar.

enter image description here

There is a track layer that the progress indicator moves along and I also have a circle that moves with the progress bar for added emphasis. Both the progress bar and the circle move correctly along the path but when the progress is complete, instead of stopping at the end of the path, the circle jumps back to the beginning of the progress path.

screen recording of behavior

The code:

class ViewController: UIViewController {
    
    let shapeLayer = CAShapeLayer()
    let trackLayer = CAShapeLayer()
    let dotLayer: CALayer = CALayer()
    let seconds = 10.0
    
    // MARK: - Properties
    private var progressBarView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "Making Progress"
        view.backgroundColor = .systemTeal
        
        // determine the value of Center
        let center = view.center
        
        let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: 2.7, endAngle: 0.45, clockwise: true) // these values give us a complete circle
        
        trackLayer.path = circularPath.cgPath
        trackLayer.strokeColor = UIColor.lightGray.cgColor
        trackLayer.lineWidth = 10
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.lineCap = .round
        
        view.layer.addSublayer(trackLayer)
        
        shapeLayer.path = circularPath.cgPath
        
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.strokeEnd = 0
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineCap = .round
        
        view.layer.addSublayer(shapeLayer)
        
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap )))
    }
    
    @objc private func handleTap() {
        print("Attempting to animate stroke")
        
        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        
        basicAnimation.toValue = 1
        
        basicAnimation.duration = seconds
        
        basicAnimation.fillMode = CAMediaTimingFillMode.forwards
        basicAnimation.isRemovedOnCompletion = false
        
        shapeLayer.add(basicAnimation, forKey: "anyKeyWillDo")
        
        // circle image
        let circleNobImage = UIImage(named: "whiteCircle.png")!
        
        dotLayer.contents = circleNobImage.cgImage
        dotLayer.bounds = CGRect(x: 0.0, y: 0.0, width: circleNobImage.size.width, height: circleNobImage.size.height)
        
        view.layer.addSublayer(dotLayer)
        
        dotLayer.position = CGPoint(x: 105, y: 460)
        
        dotLayer.opacity = 1
        
        dotAnimation()
    }
    
    func dotAnimation() {
        let dotAnimation = CAKeyframeAnimation(keyPath: "position")
        dotAnimation.path = trackLayer.path
        dotAnimation.calculationMode = .paced
        
        let dotAnimationGroup = CAAnimationGroup()
        dotAnimationGroup.duration = seconds
        
        dotAnimation.autoreverses = false
        dotAnimationGroup.animations = [dotAnimation]
        dotLayer.add(dotAnimationGroup, forKey: nil)
    }
}

I need to get the circle to stop at the end of the arc with the progress bar. How is this done?


Solution

  • Also set dotAnimationGroup.fillMode and dotAnimationGroup.isRemovedOnCompletion

    dotAnimationGroup.fillMode = .forwards
    dotAnimationGroup.isRemovedOnCompletion = false
    

    enter image description here