iosswiftswift3core-animationcakeyframeanimation

CAKeyFrameAnimation not Linear for values greater than PI


I am having some trouble to understand why an animation isn't working like expected. What I am doing is this:

  1. Create a UIBezierPath with an arc to move a Label along this path and animate the paths stroke.

    //Start Point is -.pi /2 to let the Arc start at the top.
    //self.progress = Value between 0.0 and 1.0
    let path : UIBezierPath = UIBezierPath.init(arcCenter: CGPoint.init(x: self.bounds.width * 0.5, y: self.bounds.height * 0.5), 
    radius: self.bounds.width * 0.5, startAngle: -.pi / 2, endAngle: (2 * self.progress * .pi) - (.pi / 2), clockwise: true)
    return path
    
  2. Add this path to a CAShapeLayer

    circlePathLayer.frame = bounds
    circlePathLayer.path = self.path.cgPath
    circlePathLayer.strokeStart = 0
    circlePathLayer.strokeEnd = 1
    
  3. Animate the strokeEnd property with a CABasicAnimation

    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.repeatCount = HUGE
    animation.fromValue = 0.0
    animation.toValue = 1.0
    animation.duration = self.animationDuration
    animation.isRemovedOnCompletion = false
    animation.fillMode = kCAFillModeBoth
    
  4. Animate the position property of my label with a CAKeyFrameAnimation

    let animationScore = CAKeyframeAnimation(keyPath: "position")
    //some things I tried to fix
    //animationScore.timingFunctions = [CAMediaTimingFunction(controlPoints: 0.250, 0.250, 0.750, 0.750)]
    //animationScore.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionLinear)
    animationScore.path = self.path.cgPath
    animationScore.duration = self.animationDuration
    animationScore.isRemovedOnCompletion = false
    animationScore.fillMode = kCAFillModeBoth
    animationScore.repeatCount = HUGE
    
  5. Add my animations to layer and label

    self.circlePathLayer.add(animation, forKey: nil)
    self.scoreLabel.layer.add(animationScore, forKey: nil)
    

My Problem: For ProgressValues greater than 0.75 my label is not moving in linear speed. Values greater than 0.75 mean that my arc is greater than PI. For values less than 0.75 my animation works fine and label and strokeend have the same speed and are on top of each other.

GIF : Gif showing the animation

Please ignore the 100% in the Label in this gif my progress was at a value of 0.76.

You see my Label slows down after three quarters of my circle.

I hope someone can help me. Many thanks


Solution

  • The keyframe animation introduces an unnecessary complication. Simply rotate the label around the center with the same duration as the shape layer's stroke animation:

    enter image description here

    (I apologize that my animation starts at the bottom, not the top, but I wasn't looking at your question when I wrote the code and now I'm too lazy to change it!)

    So, how is that done? It's three animations, all with the same duration:

    This is the entire animation code:

        let anim = CABasicAnimation(keyPath: "transform.rotation.z")
        anim.fromValue = 0
        anim.toValue = 5
        anim.duration = 10
        self.arm.layer.add(anim, forKey:nil)
    
        let anim2 = CABasicAnimation(keyPath: "transform.rotation.z")
        anim2.fromValue = 0
        anim2.toValue = -5
        anim2.duration = 10
        self.lab.layer.add(anim2, forKey:nil)
    
        let anim3 = CABasicAnimation(keyPath: "strokeEnd")
        anim3.fromValue = 0
        anim3.toValue = 1
        anim3.duration = 10
        self.shape.add(anim3, forKey:nil)