Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 398
Albert.Hildenberg Avatar asked Feb 10 '17 11:02

Albert.Hildenberg


1 Answers

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:

  • The shape layer's strokeEnd, like your animation.

  • An "arm" running thru the center of the circle, with the label as a sublayer at one end (so that the label appears at the radius of the circle). The arm does a rotation transform animation.

  • The label does a rotation transform animation in the opposite direction. If it didn't, it would rotate along with its superlayer. (Think of how a Ferris wheel works; your chair is on the end of the arm, but it remains upright with respect to the earth.)

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)
like image 106
matt Avatar answered Sep 24 '22 18:09

matt