I'm trying to implement an animation similar to what you can see on the image:

I'm using the Core Graphics and Core Animation with UIBezierPath to achieve this, but the problem seems to be with the start and the end of the CGPath (strokeStart always needs to be smaller than strokeEnd so the snake will not animate through the point where the path closes). After spending way too much time on this I begin to think that perhaps I'm using wrong tools for the job, any tips are welcome.
Here is the code sample I used for generating the image:
func animate() {
let centerRectInRect = {(rect: CGRect, bounds: CGRect) -> CGRect in
return CGRect(x: bounds.origin.x + ((bounds.width - rect.width) / 2.0),
y: bounds.origin.y + ((bounds.height - rect.height) / 2.0),
width: rect.width,
height: rect.height)
}
let shapeLayer = CAShapeLayer()
shapeLayer.frame = centerRectInRect(CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0), self.view.bounds)
self.view.layer.addSublayer(shapeLayer)
shapeLayer.strokeStart = 0.0
shapeLayer.strokeEnd = 1.0
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.orange.withAlphaComponent(0.2).cgColor
shapeLayer.lineWidth = 12.0
let rect = shapeLayer.bounds
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 16, height: 16))
path.append(UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 16, height: 16)))
shapeLayer.path = path.cgPath
let strokeStartAnim = CAKeyframeAnimation(keyPath: "strokeStart")
strokeStartAnim.values = [0, 1]
strokeStartAnim.keyTimes = [0, 1]
strokeStartAnim.duration = 12.0
strokeStartAnim.beginTime = 1.0
strokeStartAnim.repeatCount = .infinity
strokeStartAnim.calculationMode = .paced
let strokeEndAnim = CAKeyframeAnimation(keyPath: "strokeEnd")
strokeEndAnim.values = [0, 1]
strokeEndAnim.keyTimes = [0, 1]
strokeEndAnim.duration = 12.0
strokeEndAnim.repeatCount = .infinity
strokeEndAnim.calculationMode = .paced
let groupAnim = CAAnimationGroup()
groupAnim.animations = [strokeStartAnim, strokeEndAnim]
groupAnim.isRemovedOnCompletion = false
groupAnim.fillMode = .forwards
groupAnim.duration = .greatestFiniteMagnitude
shapeLayer.add(groupAnim, forKey: "AnimateSnake")
}
I finally managed to implement what I wanted. I don't think it's possible to do it with just one layer, so I used 2 layers and rotated the second layer 180 degrees, then synchronized the animations so that they overlap giving the effect that only one stroke is animated. Bonus - line cap can be selected from
CAShapeLayerLineCap.You use it like this:
Result: