Stroke of CAShapeLayer stops at wrong point?
Asked Answered
B

1

6

I drew a simple circular progress view like this:

let trackLayer:CAShapeLayer = CAShapeLayer()
let circularPath:UIBezierPath = UIBezierPath(arcCenter: CGPoint(x: progressView.frame.width / 2, y: 39.5), radius: progressView.layer.frame.size.height * 0.4, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 6
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = kCALineCapRound
progressView.layer.insertSublayer(trackLayer, at: 0)
let progressLayer:CAShapeLayer = CAShapeLayer()
progressLayer.path = circularPath.cgPath
progressLayer.strokeColor = UIColor.blue.cgColor
progressLayer.lineWidth = 6
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineCap = kCALineCapSquare
progressLayer.strokeEnd = 0
progressView.layer.insertSublayer(progressLayer, above: trackLayer)

And on a certain event, I try to update the progressLayer:

if let sublayers = progressView.layer.sublayers {
    (sublayers[1] as! CAShapeLayer).strokeEnd = 0.5
}

Now if I understand strokeEnd correctly, 0 is the start point of the circular path, and 1 is the end point, which means that 0.5 should make it go halfway around, correct? This is what I get instead:

Stroke of progress layer

I tested multiple values and it turns out that around 0.78 makes a full circle. Anything above that, all the way up to 1, doesn't change anything. Can anyone tell me what I'm missing here. Perhaps a problem with the start or end angle of my circular path? Or maybe I just completely misunderstood how strokeEnd works, in which case an explanation would be very much appreciated.

Bristletail answered 19/7, 2018 at 18:21 Comment(0)
A
6

Can anyone tell me what I'm missing here. Perhaps a problem with the start or end angle of my circular path?

Exactly. Think about your path (circularPath) which is assigned as the path of the shape layer. It goes from startAngle: -CGFloat.pi / 2 to endAngle: 2 * CGFloat.pi. That is more than just a complete circle. Do you see? It's a one-and-a-quarter circle!

That's why, above 0.78 (actually 0.8 or four-fifths), a change in the value of your strokeEnd appears to change nothing; you are just drawing over the start of the circle, redrawing a part of the circle that you already drew.

You probably meant

startAngle: -.pi / 2.0, endAngle: 3 * .pi / 2.0

(although what I would do is go from 0 to .pi*2 and apply a rotation transform so as to start the drawing at the top).


Another problem with your code, which could cause trouble down the line, is that you have forgotten to give either of your layers a frame. Thus they have no size.

Assault answered 19/7, 2018 at 18:28 Comment(2)
Yup, that fixed the problem! Thanks! Stupid me. When I was setting the start angle I remembered to calculate the angle from the first quarter of the circle, but for some reason I completely forgot to do this when calculating the end angle. As for the layer frames, I set the frame of the parent view (progressView) which, I assume, should be sufficient since all the layers will be bound by progressView's frame. Seems a bit overkill to set the frames of the sublayers as well, doesn't it? Anyway, never encountered any problems by not setting layer frames, so I don't think it'll be any trouble.Bristletail
"for the layer frames, I set the frame of the parent view (progressView) which, I assume, should be sufficient" It isn't, and you're wrong. Layers do not magically size themselves in some way. The drawing of the layers is appearing, but it is outside the layers themselves which have no size. Zero-size layers will get you in trouble some day (for example, you can't apply a rotation or scale transform correctly). Get in the habit now of giving a layer a frame as soon as you create it. I don't care what you've been doing in the past; go back and fix all that code too. Do it now.Assault

© 2022 - 2024 — McMap. All rights reserved.