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")
}
2
Answers
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:
Another approach, if you want to try it out…
Use a 1/4 "pie wedge" as a mask for the bordered, rounded-corner shape layer, and rotate that pie wedge layer…
Note that the "jumping" seen here is only because the recording is repeating.
One-quarter "pie wedge" shape layer:
When set as a mask:
Sample view subclass: