I'm creating a circle similar to the Android material design loading indicator seen on the right here, but the circle isn't completing itself.
class ViewController: UIViewController {
var baseLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
animate()
}
private func setUp() {
view.layer.addSublayer(baseLayer)
let basePath = UIBezierPath(arcCenter: view.center,
radius: 100,
startAngle: CGFloat.pi / 2,
endAngle: 2 * CGFloat.pi,
clockwise: true).cgPath
baseLayer.strokeColor = UIColor.gray.cgColor
baseLayer.path = basePath
baseLayer.fillColor = UIColor.clear.cgColor
baseLayer.lineWidth = 2
baseLayer.position = view.center
baseLayer.strokeEnd = 0
}
private func animate() {
CATransaction.begin()
let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeEndAnimation.toValue = 1
strokeEndAnimation.fillMode = .forwards
strokeEndAnimation.isRemovedOnCompletion = false
strokeEndAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.toValue = CGFloat.pi
rotationAnimation.fillMode = .forwards
rotationAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
rotationAnimation.isRemovedOnCompletion = false
let group = CAAnimationGroup()
group.duration = 1.5
group.repeatCount = 0
group.fillMode = .forwards
group.isRemovedOnCompletion = false
group.animations = [strokeEndAnimation, rotationAnimation]
baseLayer.add(group, forKey: "animations")
CATransaction.commit()
}
}
The image below shows what I'm talking about. It's correctly stopping at 3*pi/2 but I believe the strokeEnd is incorrectly stopping at pi. I tried various different configurations of strokeEnd and transform animations, but nothing seems to work. Modifying the strokeEnd toValue
animation doesn't change anything.
PI is one-half of the circle. If you remove the rotation, and use this as your basePath
:
basePath = UIBezierPath(arcCenter: view.center,
radius: 100,
startAngle: 0,
endAngle: 1.0 * CGFloat.pi,
clockwise: true).cgPath
The line will start at 3:00 and go to 9:00
If you start at one-half PI:
basePath = UIBezierPath(arcCenter: view.center,
radius: 100,
startAngle: CGFloat.pi / 2,
endAngle: 1.0 * CGFloat.pi,
clockwise: true).cgPath
Your line goes from 6:00 to 9:00
Add a half-PI to the endAngle:
basePath = UIBezierPath(arcCenter: view.center,
radius: 100,
startAngle: CGFloat.pi / 2,
endAngle: 1.5 * CGFloat.pi,
clockwise: true).cgPath
and you get 6:00 to 12:00, which is almost what you want.
Now you add a rotation of PI (remember, that's one-half of a full circle):
and you're at 12:00 to 6:00.
To bring the end up to 12:00, gotta add another PI
var basePath = UIBezierPath(arcCenter: view.center,
radius: 100,
startAngle: CGFloat.pi / 2,
endAngle: 2.5 * CGFloat.pi,
clockwise: true).cgPath