4

I am trying to animate a CAShapeLayer along a UIBezierPath. This works fine in circular path situations, but not when I use an oval path. The animation does happen, but pauses for a short period every time it has made a round, without me setting a delay.

Changing the size and time doesn't seem to improve on the issue, but makes the pause longer / shorter. For example, setting the duration to 1 in the animation below, the pause gets very short (in line with the speeding up of the rotation along the path).

This is the path:

let ovalPath = UIBezierPath(ovalIn: CGRect(x: -25, y: -50, width: 50, height: 100))
ovalPath.apply(CGAffineTransform(rotationAngle: 45 * .pi / 180))
ovalPath.apply(CGAffineTransform(translationX: frame.size.width / 2, y: frame.size.height / 2))

with it's ShapeLayer:

let ovalLayer = CAShapeLayer()
ovalLayer.strokeColor = UIColor.lightGray.cgColor
ovalLayer.fillColor = UIColor.clear.cgColor
ovalLayer.path = ovalPath.cgPath
view.layer.addSublayer(ovalLayer)

That shows the oval path, tilted 45 degrees. This is how I set the animation:

let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation.duration = 5
animation.repeatCount = MAXFLOAT
animation.path = ovalPath.cgPath

And finally the shape following the path:

let objectPath = UIBezierPath(arcCenter: CGPoint(x: 0 ,y: 0), radius: 50, startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let objectLayer = CAShapeLayer()
objectLayer.path = objectPath.cgPath
objectLayer.strokeColor = UIColor.darkGray.cgColor
objectLayer.fillColor = UIColor.darkGray.cgColor
view.layer.addSublayer(objectLayer)
objectLayer.add(animation, forKey: nil)

I expect it to loop infinitely without pausing (which works exactly so in circular paths). Am I missing something obvious?

EDIT: tried using the timingFunction as follows:

animation.timingFunction = CAMediaTimingFunction(name: <CAMediaTimingFunctionName>)

for example:

animation.timingFunction = CAMediaTimingFunction(name: .default)

EDIT 2:

This is what it currently looks like. The animation starts in the bottom right. Code for both ovals is exactly the same, except for the animation duration (1 vs 5 sec)

enter image description here

Harold
  • 205
  • 2
  • 11
  • Your animation has no timing function so it probably defaults to ease in ease out. Hence the pause. – matt Aug 14 '19 at 15:00
  • Thanks, I've tried that just now but I don't see any changes. I've edited my post to include the `timingFunction`. – Harold Aug 14 '19 at 15:27
  • Matt, I had the same thought, but then why would he not see the same thing when his path is a circle rather than an oval? – Duncan C Aug 14 '19 at 15:28
  • To the OP: a repeating animation with ease-in, ease-out timing doesn't exactly pause - it coasts to a stop, then coasts back to a start. The effect is a little different than a pause, which would "lurch to stop" and then jump back into motion. It should be pretty easy to tell the difference by watching the animation. – Duncan C Aug 14 '19 at 15:28
  • What you added to your question is not valid code. That's the template for creating a timing function. What is the actual code you put in your project? – Duncan C Aug 14 '19 at 15:30
  • You are right. I tried various options, for example `animation.timingFunction = CAMediaTimingFunction(name: .default)` – Harold Aug 14 '19 at 15:31
  • What I see is indeed an abrupt stop and new start in the animation, no easing in / out – Harold Aug 14 '19 at 15:32
  • Maybe: animation.isRemovedOnCompletion = false and animation.fillMode = kCAFillModeForwards – Klinki Aug 14 '19 at 15:48
  • Thanks, but that doesn't seem to solve it either – Harold Aug 14 '19 at 16:05
  • @Harold do you solve this isuue ? plz help me i am facing same issue . – balkaran singh Apr 10 '20 at 12:56

1 Answers1

3

Here is a solution (jump is just gif animation end). Tested with Xcode 11.4 / iOS 13.4

demo

let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation.duration = 5
animation.repeatCount = .greatestFiniteMagnitude // just recommended by Apple
animation.path = ovalPath.cgPath
animation.calculationMode = .paced    // << required !!
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690