Search code examples
swiftuibezierpathcashapelayercabasicanimation

CABasicAnimation around Circle goes too Far


I am looking to have an animated outer layer to a circle that will show progress. What I have below is able to do that; however, the ending animation of the outer fill goes too far. In my example I would like it go from the 12:00 position to the 6:00 position or 50% of the way around the circle. In practice it goes to about the 8:00 position.

class CircleView: UIView {

private let progressLayer = CAShapeLayer()
private var progressColor:UIColor!

required convenience init(progressColor:UIColor) {
    self.init(frame:.zero)
    self.progressColor = progressColor
}

override init(frame: CGRect) {
    super.init(frame: frame)

    self.translatesAutoresizingMaskIntoConstraints = false
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
}

override func draw(_ rect: CGRect) {
    setup()
}

private func setup() {
    let circleLayer = CAShapeLayer()

    let center = CGPoint(x: self.bounds.size.width / 2, y: self.bounds.size.height / 2)
    let path = UIBezierPath(arcCenter: center, radius: self.frame.width / 2, startAngle: -CGFloat.pi / 2, endAngle: CGFloat.pi * 2, clockwise: true)
    circleLayer.path = path.cgPath
    circleLayer.strokeColor = UIColor.gray.cgColor
    circleLayer.fillColor = UIColor.white.cgColor
    circleLayer.lineCap = CAShapeLayerLineCap.round
    circleLayer.lineWidth = 20
    circleLayer.masksToBounds = false
    self.layer.addSublayer(circleLayer)

    progressLayer.path = path.cgPath
    progressLayer.strokeColor = progressColor.cgColor
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.lineCap = CAShapeLayerLineCap.round
    progressLayer.lineWidth = 20

    circleLayer.addSublayer(progressLayer)
}

func animateProgress(percentComplete:Double) {
    let progressAnimation = CABasicAnimation(keyPath: "strokeEnd")

    progressAnimation.fromValue = 0
    progressAnimation.toValue = 0.5
    progressAnimation.duration = 2
    progressAnimation.fillMode = .forwards
    progressAnimation.isRemovedOnCompletion = false

    progressLayer.add(progressAnimation, forKey: "strokeEnd")
}

If I change to the above line to progressAnimation.toValue = 0.4 it will show the 12-6 I am looking for. I am not understanding why it would be 0.4 versus 0.5?


Solution

  • It will be OK to fix the angle .

    Turn

    let path = UIBezierPath(arcCenter: center, radius: self.frame.width / 2, startAngle: -CGFloat.pi / 2, endAngle: CGFloat.pi * 2, clockwise: true)
    

    to

    let path = UIBezierPath(arcCenter: center, radius: self.frame.width / 2, startAngle: -CGFloat.pi / 2, endAngle: 1.5 * CGFloat.pi, clockwise: true)
    

    A circle is of 2 * CGFloat.pi, not 2.5 * CGFloat.pi.

    startAngle: -CGFloat.pi / 2, endAngle: CGFloat.pi * 2 is 2.5 * CGFloat.pi.

    2 * CGFloat.pi * 0.5 equals 2.5 * CGFloat.pi * 0.4

    11