Why the view content is not centered? - ios

This is my result
Although I am explicitly stating that the arc center is the center of the view.
This is my code:
let count = colors.count-1
var index: Int = initialIndex
for oneArray in points {
let startAngleRadiant: CGFloat = degreesToRadians(Double(oneArray[0]))
let endAngleRadiant: CGFloat = degreesToRadians(Double(oneArray[1]))
let radius: CGFloat = self.frame.height/4
let path = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: startAngleRadiant,
endAngle: endAngleRadiant,
clockwise: true)
let color = UIColor(red: colors[count-index][0], green: colors[count-index][1], blue: colors[count-index][2], alpha: 1.0)
path.lineWidth = CGFloat(4)
if ++index >= count {
index = 0
}
color.setStroke()
path.stroke()
}
And the view (that's the circle inside) has the correct constrains for height and width. As you see here
This is the constrain of the view Wainting Circle View
as you see, i am setting the hight and the width relative to the supper view,
thus the center of that view should be correct, as a result, the self.center that i am using should draw at the center

You are defining the center of your arch a self.center which the docs describe as:
The center is specified within the coordinate system of its superview and is measured in points. Setting this property changes the values of the frame properties accordingly.
This is essentially the same as CGPoint(x: frame.midX, y: frame.midY), meaning that if your view isn't flush with the top of the superview superview, center.x is going to be different from the center of the view.
For example, if your view has frame == CGRect(x:50, y:50, width:100, height:100), it will have center == CGPoint(x: 50 + 100/2, y: 50 + 100 /2) == CGPoint(x:100, y:100) which is the bottom right corner of the view.
If you want the circle to be centered in the center of the view, you should pick your center based of the bounds, which is in a view-relative coordinate system:
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let path = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: startAngleRadiant,
endAngle: endAngleRadiant,
clockwise: true)

Related

UIBezierPath arc to create pie charts with rounded corners and spacing

I wonder how can we create a pie chart with rounded edges and spaces between pie as shown in the photo.
My first approach: I move the pies out of its center point an offset = 10 to make it look like the photo. But It seems like the radius of the biggest pie is smaller than the smaller ones.
Then I make a change on Radius, but the spacing a bit weird
And since the newCenter point is not in the center of superview, It’s cut off on a side.
outerRadius = outerRadius - offset * 2 * (1 - percentage)
(Percentage is the proportion of pie in the chart)
My second approach: I calculate the center point for each pie instead of moving it out of its original center point. Imagine there’s an empty middle as a circle and a new center point for each pie is in that circle.
The issues still occur with large pies.
The new center point for each slide on my tries:
let middleAngle = ((startAngle + endAngle) / 2.0).toRadians()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let newCenter = CGPoint(x: center.x + cos(middleAngle) * offset, y: center.y + sin(middleAngle) * offset)
Issues with radius and center point | Expected result
Here’s my code
https://gist.github.com/phongngo511/dfd416aaad45fc0241cd4526d80d94d6
Hi is this what you're trying to achieve? If so, I think your approach had a couple of issues. Firstly, looking at your code gist, I changed a couple of things from the way you were doing it:
Changed the pie segment sizes (so I could test >180° segments) and colours.
I added a convenience toRadians() function to the CGFloat extension (which is just the opposite of the toRadians() function you'd already added).
I changed the radius variable to be the min (not max as you were doing) of the bounds width / height, so that it fits in view without cropping. This is just personal preference & wouldn't change the overall functioning of the code (you might need it to be bigger & scrollable, for instance, whereas I just wanted to debug this particular problem). I also added padding so that it would still fit the segments when they've been spaced apart.
I went down your original route of solving the problem; draw all segments at the centre of the pie chart, and space them out afterwards, rather than trying to draw each one off centre. You could do either route although keeping them centred while constructing them is simpler and leads to more readable code. The spacing out is achieved by an affine transform at the end of the createPath: function, which spaces them out by a given segment's mid angle. You'd probably want to do it slightly more intelligently than this in real life (it's a bit primitive) as, as per the screenshot, very large segments will appear to be separated further than small segments are from each other (the red segment appears further away from the green and blue than the green and blue are from each other). So you might want to develop an algorithm that not only incorporates a segment's mid angle, but also how big that segment is, in order to determine not only the direction but also the distance to separate it? Or maybe factor in a segment's neighbours' mid angles when determining the direction of separation? Personal taste.
In your layoutSubviews(), you were supplying your createPath() with a different oRadius for each segment. That's why your segments had different radii from each other. I just supplied "radius" for all of them. If you comment out the affine transform in my createPath() function (which spaces them out), you'll see the segments in my version are all the same size radius.
I moved the path.close() into the createPath() function, rather than after calling it. Seems neater.
In terms of drawing a given segment, I've taken a different approach entirely (aside from drawing it centred in the pie chart and then moving it afterwards). I've drawn it with 2 straight lines and an arc for the outer circumference of the pie chart. For the rounded corners, rather than drawing an arc (N.B.: your centre rounded corner for a segment wasn't drawing correctly, causing weird graphical artefacts), I've used quadratic Bézier curves. These take only 1 control point, not 2 control points like a cubic Bézier curve takes. As a result, you can specify the corner of the segment as that control point, and it will give you a rounded corner suitable for the corner of the triangle that you're rounding. Because of this, I only draw the lines / arc up to near each corner, then do a quad Bézier curve to round the corner, then carry on with the rest of the segment.
Let me know if anything needs clarification, hope this helps!
import UIKit
class PieChartView: UIView {
var onTouchPie: ((_ sliceIndex: Int) -> ())?
var shouldHighlightPieOnTouch = false
var shouldShowLabels: Bool = false {
didSet { setNeedsLayout() }
}
var labelTextFont = UIFont.systemFont(ofSize: 12) {
didSet { setNeedsLayout() }
}
var labelTextColor = UIColor.black {
didSet { setNeedsLayout() }
}
var shouldShowTextPercentageFromFieFilledFigures = false {
didSet { setNeedsLayout() }
}
var pieGradientColors: [[UIColor]] = [[.red,.red], [.cyan,.cyan], [.green,.green]] {
didSet { setNeedsLayout() }
}
var pieFilledPercentages:[CGFloat] = [1, 1, 1] {
didSet { setNeedsLayout() }
}
//var segments:[CGFloat] = [40, 30, 30] {
var segments:[CGFloat] = [70, 20, 10] {
didSet { setNeedsLayout() }
}
var offset:CGFloat = 15 {
didSet { setNeedsLayout() }
}
var spaceLineColor: UIColor = .white {
didSet { setNeedsLayout() }
}
private var labels: [UILabel] = []
private var labelSize = CGSize(width: 100, height: 50)
private var shapeLayers = [CAShapeLayer]()
private var gradientLayers = [CAGradientLayer]()
override func layoutSubviews() {
super.layoutSubviews()
labels.forEach({$0.removeFromSuperview()})
labels.removeAll()
shapeLayers.forEach({$0.removeFromSuperlayer()})
shapeLayers.removeAll()
gradientLayers.forEach({$0.removeFromSuperlayer()})
gradientLayers.removeAll()
let valueCount = segments.reduce(CGFloat(0), {$0 + $1})
guard pieFilledPercentages.count >= 3, segments.count >= 3, pieGradientColors.count >= 3 , valueCount > 0 else { return }
let radius = min(bounds.width / 2, bounds.height / 2) * 0.9 //KEN CHANGED
var startAngle: CGFloat = 360
let proportions = segments.map({ ($0 / valueCount * 100).rounded()})
for i in 0..<segments.count {
let endAngle = startAngle - proportions[i] / 100 * 360
let path = createPath(from: startAngle, to: endAngle, oRadius: radius, percentage: proportions[i])
//path.close() //KEN CHANGED
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayers.append(shapeLayer)
let gradientLayer = CAGradientLayer()
gradientLayer.colors = pieGradientColors[i].map({$0.cgColor})
if i == 0 {
gradientLayer.locations = [0.5, 1]
} else {
gradientLayer.locations = [0, 0.5]
}
gradientLayer.mask = shapeLayer
gradientLayer.frame = bounds
if proportions[i] != 0 && pieFilledPercentages[i] != 0 {
layer.addSublayer(gradientLayer)
gradientLayers.append(gradientLayer)
}
let label = labelFromPoint(point: getCenterPointOfArc(startAngle: startAngle, endAngle: endAngle), andText: String(format: "%.f", shouldShowTextPercentageFromFieFilledFigures ? pieFilledPercentages[i] * 100 :segments[i]) + "%")
label.isHidden = !shouldShowLabels
if proportions[i] != 0 {
addSubview(label)
labels.append(label)
}
startAngle = endAngle
}
}
private func labelFromPoint(point: CGPoint, andText text: String) -> UILabel {
let label = UILabel(frame: CGRect(origin: point, size: labelSize))
label.font = labelTextFont
label.textColor = labelTextColor
label.text = text
return label
}
private func getCenterPointOfArc(startAngle: CGFloat, endAngle: CGFloat) -> CGPoint {
let oRadius = max(bounds.width / 2, bounds.height / 2) * 0.8
let center = CGPoint(x: oRadius, y: oRadius)
let centerAngle = ((startAngle + endAngle) / 2.0).toRadians()
let arcCenter = CGPoint(x: center.x + oRadius * cos(centerAngle), y: center.y - oRadius * sin(centerAngle))
return CGPoint(x: (center.x + arcCenter.x) / 2, y: (center.y + arcCenter.y) / 2)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, shouldHighlightPieOnTouch {
shapeLayers.enumerated().forEach { (item) in
if let path = item.element.path, path.contains(touch.location(in: self)) {
item.element.opacity = 1
onTouchPie?(item.offset)
} else {
item.element.opacity = 0.3
}
}
}
super.touchesBegan(touches, with: event)
}
private func highlightLayer(index: Int) {
shapeLayers.enumerated().forEach({$0.element.opacity = $0.offset == index ? 1: 0.3 })
}
private func createPath(from startAngle: CGFloat, to endAngle: CGFloat, oRadius: CGFloat, cornerRadius: CGFloat = 10, percentage: CGFloat) -> UIBezierPath {
let radius: CGFloat = min(bounds.width, bounds.height) / 2.0 - (2.0 * offset)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let midPointAngle = ((startAngle + endAngle) / 2.0).toRadians() //used to spread the segment away from its neighbours after creation
let startAngle = (360.0 - startAngle).toRadians()
let endAngle = (360.0 - endAngle).toRadians()
let circumference: CGFloat = CGFloat(2.0 * (Double.pi * Double(radius)))
let arcLengthPerDegree = circumference / 360.0 //how many pixels long the outer arc is of the pie chart, per 1° of a pie segment
let pieSegmentOuterCornerRadiusInDegrees: CGFloat = 4.0 //for a given segment (and if it's >4° in size), use up 2 of its outer arc's degrees as rounded corners.
let pieSegmentOuterCornerRadius = arcLengthPerDegree * pieSegmentOuterCornerRadiusInDegrees
let path = UIBezierPath()
//move to the centre of the pie chart, offset by the corner radius (so the corner of the segment can be rounded in a bit)
path.move(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * cornerRadius)))
//if the size of the pie segment isn't big enough to warrant rounded outer corners along its outer arc, don't round them off
if ((endAngle - startAngle).toDegrees() <= (pieSegmentOuterCornerRadiusInDegrees * 2.0)) {
//add line from centre of pie chart to 1st outer corner of segment
path.addLine(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * radius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * radius)))
//add arc for segment's outer edge on pie chart
path.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
//move down to the centre of the pie chart, leaving room for rounded corner at the end
path.addLine(to: CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * cornerRadius)))
//add final rounded corner in middle of pie chart
path.addQuadCurve(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * cornerRadius)), controlPoint: center)
} else { //round the corners on the outer arc
//add line from centre of pie chart to circumference of segment, minus the space needed for the rounded corner
path.addLine(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius)), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius))))
//add rounded corner onto start of outer arc
let firstRoundedCornerEndOnArc = CGPoint(x: center.x + (cos(startAngle + pieSegmentOuterCornerRadiusInDegrees.toRadians() - CGFloat(360).toRadians()) * radius), y: center.y + (sin(startAngle + pieSegmentOuterCornerRadiusInDegrees.toRadians() - CGFloat(360).toRadians()) * radius))
path.addQuadCurve(to: firstRoundedCornerEndOnArc, controlPoint: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * radius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * radius)))
//add arc for segment's outer edge on pie chart
path.addArc(withCenter: center, radius: radius, startAngle: startAngle + pieSegmentOuterCornerRadiusInDegrees.toRadians(), endAngle: endAngle - pieSegmentOuterCornerRadiusInDegrees.toRadians(), clockwise: true)
//add rounded corner onto end of outer arc
let secondRoundedCornerEndOnLine = CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius)), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * (radius - pieSegmentOuterCornerRadius)))
path.addQuadCurve(to: secondRoundedCornerEndOnLine, controlPoint: CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * radius), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * radius)))
//add line back to centre point of pie chart, leaving room for rounded corner at the end
path.addLine(to: CGPoint(x: center.x + (cos(endAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(endAngle - CGFloat(360).toRadians()) * cornerRadius)))
//add final rounded corner in middle of pie chart
path.addQuadCurve(to: CGPoint(x: center.x + (cos(startAngle - CGFloat(360).toRadians()) * cornerRadius), y: center.y + (sin(startAngle - CGFloat(360).toRadians()) * cornerRadius)), controlPoint: center)
}
path.close()
//spread the segments out around the pie chart centre
path.apply(CGAffineTransform(translationX: cos(midPointAngle) * offset, y: -sin(midPointAngle) * offset))
return path
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * CGFloat(Double.pi) / 180.0
}
func toDegrees() -> CGFloat {
return self / (CGFloat(Double.pi) / 180.0)
}
}

Fill color not working using CAShapeLayer in swift

I am trying below code to draw shape and that's okay but UIView background color not showing since I Have given orange color to View
See my code:
let myView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
myView.backgroundColor = .orange
let circlePath = UIBezierPath(arcCenter: CGPoint(x: myView.bounds.size.width / 2, y: 0), radius: myView.bounds.size.height, startAngle: 0.0, endAngle: .pi, clockwise: false)
let circleShape = CAShapeLayer()
circleShape.masksToBounds = false
circleShape.path = circlePath.cgPath
circleShape.fillColor = UIColor.orange.cgColor
circleShape.strokeColor = UIColor.orange.cgColor
myView.layer.mask = circleShape
print(myView)
Output At Playground:
Try this code:
let myView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
myView.backgroundColor = .orange
let circlePath = UIBezierPath(arcCenter: myView.center,
radius: myView.bounds.size.width / 2,
startAngle: 0.0,
endAngle: .pi,
clockwise: false)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
myView.layer.mask = circleShape
I think you don't see expecting result because of wrong arcCenter or start and end angels
result of the code:
The issue seems to be with your circlePath, that has:
1) Wrong arcCenter (CGPoint's y is not centered)
2) Radius (It should be half the view's height/width, considering height = width and you need a circle)
3) Angle (It should be 360 degrees)
Replace its definition with:
let circlePath = UIBezierPath(arcCenter: myView.center, radius: myView.bounds.height/2, startAngle: 0.0, endAngle: .pi * 2, clockwise: false)
and it should work as intended...
that's okay but UIView background color not showing since I Have given
orange color to View
The path you've used doesn't even display a layer since you've specified its corner radius to be the same as its height, and you've applied the generated supposedly invisible layer to be your view's mask.
A few observations:
You are just looking at a visual representation of the path, not of the view. To see the view, you should set the liveView of the PlaygroundPage:
import PlaygroundSupport
And
PlaygroundPage.current.liveView = myView
PlaygroundPage.current.needsIndefiniteExecution = true
An an aside, your circle view is going from 0 to π counterclockwise. I.e. that is the top half of a circle. So if the circle’s center has a y of 0, that means your path is above the view and doesn’t overlap with it. Either set it to go clockwise (to get the bottom half of a circle) or move the y down (so the top half half of the circle overlaps the view).
I find that playgrounds resize their liveView (even if I set wantsFullScreenLiveView to false), so if I want a view of a particular size, I use a “container view”. I.e., I will add myView as a subview of containerView, add constraints to put it in the center of that container, and then set containerView as the liveView.
Only tangentially related, but you’re setting the color of the shape layer of the mask to be orange. Masks only use their alpha channel, so using orange is meaningless. We’d generally set masks to be white, but I just wanted to make sure we didn’t conflate the color of the mask with the color of the masked view.
So, pulling that all together:
import UIKit
import PlaygroundSupport
// create container
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
// create view that we'll mask with shape layer
let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
myView.backgroundColor = .orange
myView.clipsToBounds = true
containerView.addSubview(myView)
// create mask
let circlePath = UIBezierPath(arcCenter: CGPoint(x: rect.midX, y: rect.minY), radius: rect.height, startAngle: 0, endAngle: .pi, clockwise: true)
let circleShape = CAShapeLayer()
circleShape.masksToBounds = false
circleShape.path = circlePath.cgPath
circleShape.fillColor = UIColor.white.cgColor
circleShape.strokeColor = UIColor.white.cgColor
myView.layer.mask = circleShape
// center masked view in container
NSLayoutConstraint.activate([
myView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
myView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
myView.widthAnchor.constraint(equalToConstant: 200),
myView.heightAnchor.constraint(equalToConstant: 200)
])
// show it
PlaygroundPage.current.liveView = containerView
PlaygroundPage.current.needsIndefiniteExecution = true
That yields:
So, by making the arc go clockwise and by using the liveView, we can now see it. Obviously an arc whose center is at midX, minY and with a radius of height, cannot show the full half circle. You’d have to set the radius to be width / 2 (or increase the width of the rect or whatever) if you wanted to see the full half circle.
Just remove path.move(to:) calls everywhere after drawing. That's helped me with the same issue

How to centre a subview in UIView?

I am trying to create a timer. I have a circle using UIBezierPath which I will animate to show the time remaining. This is the code that draws the shape and adds it to the view:
func drawBgShape() {
bgShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: center.x , y: center.y), radius:
bounds.width/2, startAngle: -.pi/2, endAngle: 1.5*.pi, clockwise: true).cgPath
bgShapeLayer.strokeColor = UIColor.red.cgColor
bgShapeLayer.fillColor = UIColor.clear.cgColor
bgShapeLayer.lineWidth = 10
layer.addSublayer(bgShapeLayer)
}
However, when the code is run, it looks like this;
I have tried a number of ways to centre the progress bar but none of them seem to work. For example, using frame.height/2 doesn't have any effect.
How can I centre the progress bar?
Thanks.
EDIT:
bgShapeLayer is defined like this:
let bgShapeLayer = CAShapeLayer()
The issue is probably this phrase:
arcCenter: CGPoint(x: center.x , y: center.y)
A view center is where the view is located in its superview. That’s not what you want. You want the center of the view. Try this:
arcCenter: CGPoint(x: bounds.midX , y: bounds.midY)
However, it would be better if you gave your shape layer a frame, size, and position and did everything in terms of the shape layer (which is, after all, where the shape is being drawn). For example:
bgShapeLayer.frame = self.layer.bounds
self.layer.addSublayer(bgShapeLayer)
bgShapeLayer.path = UIBezierPath(
arcCenter: CGPoint(x: bgShapeLayer.bounds.midX, y: bgShapeLayer.bounds.midY),
radius: bgShapeLayer.bounds.width/2,
startAngle: -.pi/2, endAngle: 1.5*.pi, clockwise: true).cgPath
That way we do not confuse the view coordinates and the layer coordinates, as your code does. To some extent you can get away with this because it happens that in this situation there is a general equivalence of the view internal coordinates, its layer internal coordinates, and the bgShapeLayer internal coordinates, but such confusion is not a good habit to get into. You should say what you mean rather than relying on a contingency.

Masking a CAShapeLayer creates this weird artifact?

I want to create a smooth progress bar for a to-do list. I used a circle (CGMutablePath) that masks the gray area and there's an obvious arc-like artifact. Not only that but there's also an artifact on the right side of the bar.
Note: I tried to rasterize the layers to no avail.
What causes iOS to do this and how can I smooth this out or get rid of it?
private var circleMask: CAShapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = GradientColor(.leftToRight, frame: self.bounds, colors: GradientFlatRed())
layer.cornerRadius = bounds.height / 2
plainProgressBar.layer.cornerRadius = layer.cornerRadius
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
plainProgressBar.layer.shouldRasterize = true
plainProgressBar.layer.rasterizationScale = UIScreen.main.scale
createMask()
}
private func createMask() {
let path = CGMutablePath()
path.addArc(center: CGPoint(x: bounds.origin.x+25/2, y: bounds.origin.y+25/2), radius: 25/2, startAngle: 0, endAngle: CGFloat(Double.pi*2), clockwise: false)
path.addRect(bounds)
circleMask.path = path
circleMask.backgroundColor = plainMeterColor.cgColor
circleMask.fillRule = kCAFillRuleEvenOdd
plainProgressBar.layer.mask = circleMask
}
The issue is clearly an error in composition of the antialiased edges.
Knee-jerk question: do you have an atypical blend mode set?
Easiest solution: don't use path.addArc and path.addRect to generate two completely distinct shapes. Just use one of the init(roundedRect:... methods and set a corner radius of half your height, stretching the total path beyond the available space to the left to create a hard edge.
Or, if that doesn't appear, construct the path manually as a move, addLine(to:, addArc of only half a circle, then a final addLine(to: and a close. E.g. (thoroughly untested, especially re: start and end angles and clockwise versus anticlockwise versus iOS's upside-down coordinate system)
private func createMask() {
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y:0))
path.addLine(to: CGPoint(x: bounds.origin.x+25/2, y: 0))
path.addArc(center: CGPoint(x: bounds.origin.x+25/2, y: bounds.origin.y+25/2), radius: 25/2, startAngle: CGFloat(Double.pi/2.0), endAngle: CGFloat(Double.pi*3.0/2.0), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: 25))
path.close()
...

Adding curves to the ends of a UIBezierPath(arcCentre)

I have the following code added in the drawRect function of a UIView creating the following attached green arc.
Is there a way I can make the edges at the ends of the curve to look like the red arc rounded edges in the smaller image attached.
[![//Draw the Interior
let center = CGPoint(x: bounds.width/2,
y: bounds.height/2)
//Calculate the radius based on the max dimension of the view.
let radius = max(bounds.width, bounds.height)
//Thickness of the Arc
let arcWidth: CGFloat = 76
//Start and End of Circle angle
let startAngle: CGFloat = 3 * π/4
let endAngle: CGFloat = π/4
let path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
path.lineWidth = arcWidth
counterColor.setStroke()
path.stroke()]
Set the lineCapStyle property of the bezier path to
.Round:
A line with a rounded end. Quartz draws the line to extend beyond the
endpoint of the path. The line ends with a semicircular arc with a
radius of 1/2 the line’s width, centered on the endpoint.
path.lineCapStyle = .Round

Resources