Harold
Harold

Reputation: 205

Animating a CAShapeLayer along an oval UIBezierPath

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

Upvotes: 4

Views: 840

Answers (1)

Asperi
Asperi

Reputation: 257543

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 !!

Upvotes: 3

Related Questions