I am creating the circle below using a UIBezierPath. Notice that that there are two different colors on opposite sides of the circle. I would like to round the top and bottom of each little rectangle in the picture.
I want each one of the rectangles' (they are technically dashes) to have a rounded top and bottom. Like this:
Currently I am using this code to make the circle:
let bounds = self.view.bounds
let arcCenter = CGPoint(x: bounds.midX, y: bounds.midY)
let whiteLayer = CAShapeLayer()
let redLayer = CAShapeLayer()
whiteLayer.fillColor = UIColor.clear.cgColor
whiteLayer.strokeColor = UIColor.white.cgColor
var whiteRing = UIBezierPath(arcCenter: arcCenter, radius: CGFloat(125), startAngle: 0, endAngle: CGFloat.pi, clockwise: false)
whiteRing.rotateAroundPoint(angle: CGFloat.pi/2, center: arcCenter)
whiteLayer.lineWidth = 10
whiteLayer.lineDashPattern = [2, 3]
whiteLayer.path = whiteRing.cgPath
redLayer.fillColor = UIColor.clear.cgColor
redLayer.strokeColor = UIColor.red.cgColor
var redRing = UIBezierPath(arcCenter: arcCenter, radius: CGFloat(125), startAngle: 0, endAngle: CGFloat.pi, clockwise: true)
redRing.rotateAroundPoint(angle: CGFloat.pi/2, center: arcCenter)
redLayer.lineWidth = 10
redLayer.lineDashPattern = [2, 3]
redLayer.path = redRing.cgPath
self.view.layer.addSublayer(whiteLayer)
self.view.layer.addSublayer(redLayer)
I honestly don't know if this is possible with a UIBezierPath, I was trying to use something like a CAReplicatorLayer to accomplish this, but I can't seem to figure out how to consistently get the same kind of spacing in between each dash/rectangle and the two separate parts of the circle to avoid overlapping of the white and red parts. Doing this without having to have two different colors would be much easier, but for my use case I must have two different colors. So is there anyway to do this with UIBezierPath, and if not, how would I use CAReplicatorLayer to create the circle in the first picture with each little dash/rectangle having a rounded top and bottom?
Rather than dashing a circular path, stroke a series of line segments going out from the center of the circle:
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let path = UIBezierPath()
let maxRadius = (min(bounds.width, bounds.height) - lineWidth) / 2
let minRadius = maxRadius - tickLength + lineWidth
for i in 0 ..< tickCount {
let angle = startAngle + (endAngle - startAngle) * CGFloat(i) / CGFloat(tickCount)
let startPoint = center.point(at: angle, distance: minRadius)
let endPoint = center.point(at: angle, distance: maxRadius)
path.move(to: startPoint)
path.addLine(to: endPoint)
}
That uses this method for calculating the start and end points of all of those line segments:
private extension CGPoint {
func point(at angle: CGFloat, distance: CGFloat) -> CGPoint {
return CGPoint(x: x + distance * cos(angle), y: y + distance * sin(angle))
}
}
Anyway, you can then apply a line width and corner rounding when you create your shape layer:
let shapeLayer = CAShapeLayer()
shapeLayer.lineCap = .round
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = ...
shapeLayer.path = path.cgPath
Now, the problem with this is if you want to animate the changes in the red/white strokes. If you need to do that, another approach is to consider making the tick marks a mask, and animate the red stroke underneath it. E.g.
CAShapeLayer
for the red path, and animate that:That way, you get corner rounding on your tick marks, but you can still perform animations. And it eliminates any issues associated with getting the tick marks to line up, because you have a single path for all the tick marks.