How can I make this loading indicator using CABasicAnimation? I was trying to do like this

but it's working not correctly, I am not sure that adding four layers with specific paths and animations is a good idea. I need to make exactly the same I see on the picture. I hope, it's doable using only one layer with some magic
private func setupView() {
let firstLayer = createLayer()
let secondLayer = createLayer()
let thirdLayer = createLayer()
let fourthLayer = createLayer()
firstLayer.path = createBezierPath(
layerWidth: firstLayer.lineWidth,
startAngle: 0,
endAngle: 0.25
).cgPath
secondLayer.path = createBezierPath(
layerWidth: secondLayer.lineWidth,
startAngle: 0.25,
endAngle: 0.5
).cgPath
thirdLayer.path = createBezierPath(
layerWidth: thirdLayer.lineWidth,
startAngle: 0.5,
endAngle: 0.75
).cgPath
fourthLayer.path = createBezierPath(
layerWidth: thirdLayer.lineWidth,
startAngle: 0.75,
endAngle: 1
).cgPath
firstLayer.add(createAnimation(), forKey: "firstLayer")
secondLayer.add(createAnimation(), forKey: "secondLayer")
thirdLayer.add(createAnimation(), forKey: "thirdLayer")
fourthLayer.add(createAnimation(), forKey: "thirdLayer")
self.layer.addSublayer(firstLayer)
self.layer.addSublayer(secondLayer)
self.layer.addSublayer(thirdLayer)
self.layer.addSublayer(fourthLayer)
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.beginTime = 0
animation.duration = 1
animation.fromValue = CGFloat.angle(progress: 0)
animation.toValue = CGFloat.angle(progress: 0.25)
animation.timingFunction = CAMediaTimingFunction(name: .easeIn)
animation.fillMode = .forwards
animation.repeatDuration = .infinity
self.layer.add(animation, forKey: "mainAnim")
}
private func createBezierPath(
layerWidth: CGFloat,
startAngle: CGFloat,
endAngle: CGFloat
) -> UIBezierPath {
return .init(
arcCenter: CGPoint(x: bounds.width / 2, y: bounds.height / 2),
radius: (bounds.height - layerWidth) / 2,
startAngle: .angle(progress: startAngle),
endAngle: .angle(progress: endAngle),
clockwise: true
)
}
private func createAnimation() -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.beginTime = 0
animation.duration = 1
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: .easeIn)
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.repeatDuration = .infinity
return animation
}
private func createLayer() -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.orange.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 5
shapeLayer.lineCap = .round
shapeLayer.strokeEnd = 0
return shapeLayer
}
private extension CGFloat {
static var initialAngle: CGFloat = -(.pi / 2)
static func angle(progress: CGFloat) -> CGFloat {
.pi * 2 * progress + .initialAngle
}
}
I'd use a motion design tool to recreate that. For example, I think you could create this animation with the free version of Flow, which can export Swift code or Lottie files.