CGPath intersection(_:using:) and friends (new in iOS 16) broken? - ios

There is new functionality in iOS 16 for CGPath (Core Graphics), more precisely there is a function intersection(_:using:) for determining the intersection of two CGPaths (see https://developer.apple.com/documentation/coregraphics/cgpath/3994964-intersection) and several related functions for union, normalization, separating a path into its components etc.
I tested several of them and nothing seems to work. In fact, any randomly composed non-trivial example gives me wrong results. For example, the following code gives me the first picture below: The purple area is the correct intersection.
var pathRedFill = CGMutablePath(rect: CGRect(origin: CGPoint(x: 10, y: 10),
size: CGSize(width: 400, height: 700)),
transform: nil)
pathRedFill.addQuadCurve(to: CGPoint(x: 267, y: 121), control: CGPoint(x: 395, y: 735))
pathRedFill.closeSubpath()
var pathBlueFill = CGMutablePath(rect: CGRect(origin: CGPoint(x: 120, y: 120),
size: CGSize(width: 200, height: 200)), transform: nil)
pathBlueFill.addQuadCurve(to: CGPoint(x: 637, y: 176), control: CGPoint(x: 343, y: 451))
pathBlueFill.closeSubpath()
context.setBlendMode(.normal)
context.setFillColor(red: 1, green: 0, blue: 0, alpha: 0.5)
context.addPath(pathRedFill)
context.drawPath(using: .eoFill)
context.setFillColor(red: 0, green: 0, blue: 1, alpha: 0.5)
context.addPath(pathBlueFill)
context.drawPath(using: .eoFill)
If I add the following code to calculate the intersection path, I get the second picture. The green line should exactely circle the purple area. But it does not.
context.setLineWidth(4)
context.setStrokeColor(red: 0, green: 1, blue: 0, alpha: 1)
let intersectionPath = pathRedFill.intersection(pathBlueFill, using: .evenOdd)
context.addPath(intersectionPath)
context.drawPath(using: .stroke)
My questions:
Is there something that I am doing wrong? (The documentation does not put any requirements on my two paths that I intersect, so my paths are not too complex I should think...)
Has anyone experienced similar bugs?
Thank you very much for your help!
EDIT:
I have done more tests of all the 10 functions added in iOS16 (with correcting the mistake in my path formulation according to DonMag), and tried to find the most simple case where I think something goes wrong. So this (all the other code stays the same, only the paths change)
let pathRedFill = CGPath(rect: CGRect(origin: CGPoint(x: 200, y: 300),
size: CGSize(width: 200, height: 200)),
transform: nil)
var pathBlueFill = CGMutablePath()
pathBlueFill.move(to: CGPoint(x: 302.854156, y: 369.438843))
pathBlueFill.addQuadCurve(to: CGPoint(x: 296.932526, y: 386.031342), control: CGPoint(x: 218.5, y: 376.0))
pathBlueFill.closeSubpath()
gives the result:
The green line should circle the purple but does not.
This first was part of an example where there were lots of self-intersections both for the red and for the blue path and I thought the self-intersections of the paths were the problem (since I had some other quite complex examples without self-intersections, but with holes, where everything seemed to go fine). But when I was done stripping to minimum, it turns out that there is something else at odds, as there is no more self-intersections in either path...
When rounding the numbers of the blue path, the error goes away, so it is this kind of degenerate corner case regarding the roots maybe... Also my blue curve is a quadratic (and then there is the line to close the path) whereas it turns out the intersection, stroked in green, is a cubic Bezier. I am not sure if I am still doing something wrong...

Several possibilities...
the new path intersection function (and maybe others) are a little "buggy"
it's working correctly, but our understanding of how the paths are built is a bit fuzzy
a combination of 1 & 2
After some experimentation, I get this with your original code on iOS (not sure why it's flipped vertically -- are you running this on MacOS app?):
Now, as I understand it, this line:
var pathRedFill = CGMutablePath(rect: CGRect(origin: CGPoint(x: 10, y: 10),
size: CGSize(width: 400, height: 700)),
transform: nil)
is equivalent to this:
var pathRedFill = CGMutablePath()
pathRedFill.move(to: CGPoint(x: 10, y: 10))
pathRedFill.addLine(to: CGPoint(x: 410, y: 10))
pathRedFill.addLine(to: CGPoint(x: 410, y: 710))
pathRedFill.addLine(to: CGPoint(x: 10, y: 710))
pathRedFill.closeSubpath()
The next thing we do is:
pathRedFill.addQuadCurve(to: CGPoint(x: 267, y: 121), control: CGPoint(x: 395, y: 735))
pathRedFill.closeSubpath()
And we get our expected output... but, when we complete the rest of the code we're getting (what appears to be) an incorrect intersection.
My guess is that there is some sort of "disconnect" when calling .addQuadCurve after the first subpath has been closed.
If we make this change:
var pathRedFill = CGMutablePath(rect: CGRect(origin: CGPoint(x: 10, y: 10),
size: CGSize(width: 400, height: 700)),
transform: nil)
// add this line:
pathRedFill.move(to: pathRedFill.currentPoint)
pathRedFill.addQuadCurve(to: CGPoint(x: 267, y: 121), control: CGPoint(x: 395, y: 735))
pathRedFill.closeSubpath()
var pathBlueFill = CGMutablePath(rect: CGRect(origin: CGPoint(x: 120, y: 120),
size: CGSize(width: 200, height: 200)), transform: nil)
// add this line:
pathBlueFill.move(to: pathBlueFill.currentPoint)
pathBlueFill.addQuadCurve(to: CGPoint(x: 637, y: 176), control: CGPoint(x: 343, y: 451))
pathBlueFill.closeSubpath()
here's the output:
So it seems that calling .moveTo perhaps "explicitly starts a new subpath."
And then, internally, the paths/subpaths are more... complete?
Edit
For the second part of your question...
I think paths can be much more complex than it would seem on the surface.
For your specific example:
let pathRedFill = CGPath(rect: CGRect(origin: CGPoint(x: 200, y: 300),
size: CGSize(width: 200, height: 200)),
transform: nil)
var pathBlueFill = CGMutablePath()
pathBlueFill.move(to: CGPoint(x: 302.854156, y: 369.438843))
pathBlueFill.addQuadCurve(to: CGPoint(x: 296.932526, y: 386.031342), control: CGPoint(x: 218.5, y: 376.0))
pathBlueFill.closeSubpath()
pathRedFill is a clockwise path, and pathBlueFill is a counter-clockwise path.
To see the difference, try changing the clockwise var in this code... when set to true we get a correct intersectionPath:
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext()
else { return }
var pathRedFill = CGMutablePath()
var pathBlueFill = CGMutablePath()
// 200 x 300 rect path
pathRedFill = CGMutablePath(rect: CGRect(origin: CGPoint(x: 200, y: 300),
size: CGSize(width: 200, height: 200)),
transform: nil)
// switch between true and false to see the difference
let clockwise: Bool = false
var y1: CGFloat = 369.438843
var y2: CGFloat = 386.031342
if clockwise {
let bez = UIBezierPath(cgPath: pathBlueFill)
if let p = bez.reversing().cgPath.mutableCopy() {
pathBlueFill = p
}
}
pathBlueFill = CGMutablePath()
pathBlueFill.move(to: CGPoint(x: 302.854156, y: y1))
pathBlueFill.addQuadCurve(to: CGPoint(x: 296.932526, y: y2), control: CGPoint(x: 218.5, y: 376.0))
pathBlueFill.closeSubpath()
context.setBlendMode(.normal)
context.setFillColor(red: 1, green: 0, blue: 0, alpha: 0.5)
context.addPath(pathRedFill)
context.drawPath(using: .eoFill)
context.setFillColor(red: 0, green: 0, blue: 1, alpha: 0.5)
context.addPath(pathBlueFill)
context.drawPath(using: .eoFill)
context.setLineWidth(1)
context.setStrokeColor(red: 0, green: 1, blue: 0, alpha: 1)
let intersectionPath = pathRedFill.intersection(pathBlueFill, using: .evenOdd)
context.addPath(intersectionPath)
context.drawPath(using: .stroke)
}
Edit 2
The above was meant to demonstrate that mixing clockwise and counter-clockwise subpaths can be problematic. I swapped the y1 and y2 values, but didn't take into account the controlPoint y value.
To avoid changing the geometry of the blue path, instead of swapping the y1 and y2 values, we can try reversing the path:
pathBlueFill.closeSubpath()
// reverse the blue path so it's going clockwise
let bez = UIBezierPath(cgPath: pathBlueFill)
if let p = bez.reversing().cgPath.mutableCopy() {
pathBlueFill = p
}
Now we get (I believe) the same geometry, along with the desired intersection path:

Related

Draw Triangle With Curved Side And Straight Sides Using UIBezierPath and CAShapeLayer In Swift

Background
I have some simple code that draws a triangle with three straight sides using UIBezierPath and CAShapeLayer. The result is in image A, below.
However, I want the longest side of the triangle to have an inwards curve. The desired result is in image B, below.
Question
What change to the code below can achieve the longest side of the triangle to have an inwards curve? How do I instruct the path to curve?
Code
func drawShape() {
let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 50, y: 600))
path1.addLine(to: CGPoint(x: 300, y: 600))
path1.addLine(to: CGPoint(x: 300, y: 50))
path1.close()
let shape1 = CAShapeLayer()
shape1.path = path1.cgPath
shape1.fillColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1).cgColor
view.layer.insertSublayer(shape1, at: 1)
}
Image
I have added addQuadCurve before closing the path. It gives the desired result.
Code
func drawShape() {
let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 50, y: 600))
path1.addLine(to: CGPoint(x: 300, y: 600))
path1.addLine(to: CGPoint(x: 300, y: 50))
path1.addQuadCurve(to: CGPoint(x: 50, y: 600), controlPoint: CGPoint(x: (300-50), y: (600*0.5)))
path1.close()
let shape1 = CAShapeLayer()
shape1.path = path1.cgPath
shape1.fillColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1).cgColor
view.layer.insertSublayer(shape1, at: 1)
}
Image

What would be equal in Swift to draw path/rect with a specific color

in Android (in Java) I could do something like this (here I tried to draw Triangle figure which is filled with different colors, first half with red color and second with green, also black stroke around Triangle figure:
#Override
public void onDraw(Canvas canvas) {
....
canvas.clipPath(pathBoundary); // triangle figure path
canvas.drawPath(pathBoundary, paintBlack); // draw black stroke around figure
canvas.drawRect(rectRed, paintRed); // fill first half with red color
canvas.drawRect(rectGreen, paintGreen); // fill second half with green color
in iOS I just learned how to draw Triangle figure with only one color, would like to also draw with two colors (like in Android)
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
context.move(to: CGPoint(x: rect.minX, y: rect.maxY))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
context.addLine(to: CGPoint(x: (rect.maxX / 2.0), y: rect.minY))
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.60)
context.fillPath()
context.clip()
... how to draw path with specific color?
... how to draw rect with specific color?
You can do just like you do on Android: stroke the triangle path, set it as clipping path, stroke a rectangle in red, stroke a rectangle in green.
UIGraphicsBeginImageContext(CGSize(width: 200, height: 200))
let context = UIGraphicsGetCurrentContext()!
context.beginPath()
context.move(to: CGPoint(x: 100, y: 0))
context.addLine(to: CGPoint(x: 0, y: 200))
context.addLine(to: CGPoint(x: 200, y: 200))
context.closePath()
let path = context.path!
context.setStrokeColor(UIColor.black.cgColor)
context.strokePath()
context.addPath(path)
context.clip()
context.setFillColor(UIColor.red.cgColor)
context.fill(CGRect(x: 0, y: 0, width: 200, height: 150))
context.setFillColor(UIColor.green.cgColor)
context.fill(CGRect(x: 0, y: 150, width: 200, height: 50))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
You can run the code above in a playground. In the right column hover over the last line (the one for image), click on the eye icon and you'll see a preview of the image that was drawn.
The way you do it is basically by drawing two different paths. Each with a different fill color. Here is an example:
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
let leftVertex = CGPoint(x: rect.minX, y: rect.maxY)
let topVertex = CGPoint(x: rect.midX, y: rect.minY)
let r = sqrt(pow(leftVertex.x - topVertex.x, 2) + pow(leftVertex.y - topVertex.y, 2))
let leftAngle = atan(rect.maxY/rect.midX)
context.beginPath()
context.move(to: leftVertex)
context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
context.addLine(to: topVertex)
context.closePath()
context.setFillColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1)
context.fillPath()
let smallLeftVertex = CGPoint(x: r/2*cos(leftAngle), y: rect.midY)
context.beginPath()
context.move(to: smallLeftVertex)
context.addLine(to: topVertex)
context.addLine(to: CGPoint(x: rect.maxX - smallLeftVertex.x, y: rect.midY))
context.closePath()
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1)
context.fillPath()
}
There is some math to evaluate which would be the vertices of the inner (smaller) triangle.
Here is the result:
If you want you can try it in a playground:
//: Playground - noun: a place where people can play
import UIKit
class TriangleView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
let leftVertex = CGPoint(x: rect.minX, y: rect.maxY)
let topVertex = CGPoint(x: rect.midX, y: rect.minY)
let r = sqrt(pow(leftVertex.x - topVertex.x, 2) + pow(leftVertex.y - topVertex.y, 2))
let leftAngle = atan(rect.maxY/rect.midX)
context.beginPath()
context.move(to: leftVertex)
context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
context.addLine(to: topVertex)
context.closePath()
context.setFillColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1)
context.fillPath()
let smallLeftVertex = CGPoint(x: r/2*cos(leftAngle), y: rect.midY)
context.beginPath()
context.move(to: smallLeftVertex)
context.addLine(to: topVertex)
context.addLine(to: CGPoint(x: rect.maxX - smallLeftVertex.x, y: rect.midY))
context.closePath()
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1)
context.fillPath()
}
}
let triangle = TriangleView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
triangle.backgroundColor = .white
**
Edit:
**
You can also avoid to draw two overlapping triangles by using the clip feature of CGContext. In this case the code would look like this:
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
let leftVertex = CGPoint(x: rect.minX, y: rect.maxY)
let topVertex = CGPoint(x: rect.midX, y: rect.minY)
var path = CGMutablePath()
path.move(to: leftVertex)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: topVertex)
path.closeSubpath()
context.addPath(path)
context.clip()
context.setFillColor(red: 1, green: 0, blue: 0, alpha: 1)
context.fill(CGRect(x: 0, y: 0, width: rect.width, height: rect.height/2))
context.setFillColor(red: 0, green: 1, blue: 0, alpha: 1)
context.fill(CGRect(x: 0, y: rect.midY, width: rect.width, height: rect.height/2))
}
In the first part of the code we create the triangle, we extract the path just drown in the context and we add it as clipping path. Then we can fill the two rectangles with the two different colours.
Hope this answers your question.

How to create a triangle UIImage

How can I create a triangle UIImage? Here's how I'm doing it now, but it's not producing any image at all.
extension UIImage {
static func triangle(side: CGFloat, color: UIColor)->UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: side, height: side), false, 0)
let ctx = UIGraphicsGetCurrentContext()!
ctx.saveGState()
ctx.beginPath()
ctx.move(to: CGPoint(x: side / 2, y: 0))
ctx.move(to: CGPoint(x: side, y: side))
ctx.move(to: CGPoint(x: 0, y: side))
ctx.move(to: CGPoint(x: side / 2, y: 0))
ctx.closePath()
ctx.setFillColor(color.cgColor)
ctx.restoreGState()
let img = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return img
}
}
Your path does not contain any lines, so there's no region to fill. In addition you are not drawing the path.
Try something like this:
static func triangle(side: CGFloat, color: UIColor)->UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: side, height: side), false, 0)
let ctx = UIGraphicsGetCurrentContext()!
ctx.saveGState()
ctx.beginPath()
ctx.move(to: CGPoint(x: side / 2, y: 0))
//### Add lines
ctx.addLine(to: CGPoint(x: side, y: side))
ctx.addLine(to: CGPoint(x: 0, y: side))
//ctx.addLine(to: CGPoint(x: side / 2, y: 0)) //### path is automatically closed
ctx.closePath()
ctx.setFillColor(color.cgColor)
ctx.drawPath(using: .fill) //### draw the path
ctx.restoreGState()
let img = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return img
}
You might just use UIBezierPath and not use CoreGraphics at all. Furthermore, nowadays we’d use UIGraphicsImageRenderer:
extension UIImage {
static func triangle(side: CGFloat, color: UIColor) -> UIImage {
return UIGraphicsImageRenderer(size: CGSize(width: side, height: side)).image { _ in
let path = UIBezierPath()
path.move(to: CGPoint(x: side / 2, y: 0))
path.addLine(to: CGPoint(x: side, y: side))
path.addLine(to: CGPoint(x: 0, y: side))
path.close()
color.setFill()
path.fill()
}
}
}
For UIBezierPath version that still uses UIGraphicsBeginImageContextWithOptions, see previous revision of this answer.
OOPer told you about your lack of lines, and failure to actually draw the triangle.
In addition to that, I would suggest avoiding mucking around with graphics contexts. UIBezierPath makes it cleaner and easier to draw without having to worry about contexts. Also, you don't need to save and restore contexts since you are creating a temporary context and then immediately discarding it.
Here is a complete playground that draws your triangle (and fills and strokes the image rectangle so it's easier to see - you'd remove the stroke and probably fill the shape with UIColor.clear instead.)
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
extension UIImage {
static func triangle(side: CGFloat, color: UIColor)->UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: side, height: side), false, 0)
//First fill the bounds with white
let rectPath = UIBezierPath(rect: CGRect(x:0, y: 0, width: side, height: side))
UIColor.white.setFill() //Use clear if you want your image to only contain the triangle.
rectPath.fill()
//Now build the triangle path
let path = UIBezierPath()
path.move(to: CGPoint(x: side / 2, y: 0))
path.addLine(to: CGPoint(x: side, y: side))
path.addLine(to: CGPoint(x: 0, y: side))
path.close() //Closing the path draws the final line
color.setFill()
path.fill()
//Stroke the outer path in gray so you can see the bounds - remove if desired
UIColor.gray.setStroke()
rectPath.stroke()
let img = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return img
}
}
let imageView = UIImageView(frame: CGRect(x:0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = imageView
imageView.image = UIImage.triangle(side: 200.0, color: UIColor.yellow)

How do a gradient path that setting the colors from path begin to end

I want to draw a gradient path that from a color in the begin and end in another color.
Here are two ways to draw gradient line but the effect neither I want.
Someone has any idea to do this?
guard let c = UIGraphicsGetCurrentContext() else {
fatalError("Current Context NOT Founds.")
}
c.setLineWidth(width)
c.setLineCap(cap)
c.setLineJoin(join)
// the way no.1
c.saveGState()
c.move(to: CGPoint(x: 50, y: 50))
c.addLine(to: CGPoint(x: 50, y: 200))
c.addLine(to: CGPoint(x: 150, y: 200))
let alphas: [CGFloat] = [0, 1]
let colors = [UIColor(white: 1, alpha: alphas[0]).cgColor, UIColor(white: 1, alpha: alphas[1]).cgColor] as CFArray
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors, locations: [0.0, 1.0])
c.addRect(.infinite)
c.replacePathWithStrokedPath()
c.clip()
c.drawLinearGradient(gradient!, start: CGPoint(x: 0, y: 0), end: CGPoint(x: rect.width, y: rect.height), options: CGGradientDrawingOptions(rawValue: 0))
c.restoreGState()
// the way no.2
c.saveGState()
c.beginTransparencyLayer(in: rect, auxiliaryInfo: nil)
c.move(to: CGPoint(x: 50, y: 250))
c.addLine(to: CGPoint(x: 50, y: 450))
c.addLine(to: CGPoint(x: 130, y: 350))
c.setStrokeColor(UIColor.white.cgColor)
c.strokePath()
c.setBlendMode(.sourceIn)
c.drawLinearGradient(gradient!, start: CGPoint(x: 50, y: 220), end: CGPoint(x: 50, y: 450), options: CGGradientDrawingOptions(rawValue: 0))
c.endTransparencyLayer()
c.restoreGState()
last update 2017/05/26
Here are some sample images that the effect is similar to I want.

How can I create arc shape ios swift

I have an application with some buttons. I want to create a arc shape button like the following image
How can I do it? I use the following code to achieve this.
button.layer.cornerRadius = button.bounds.size.height
and
button.layer.cornerRadius = 0.5*button.bounds.size.height
I also tried this with the using of width
button.layer.cornerRadius = button.bounds.size.width
and
button.layer.cornerRadius = 0.5*button.bounds.size.width
How can I do it? Please someone help me to solve this.
To create arc shape you need to use UIBezierPath
please read apple's UIBezierPath_class Document.
They have mention method for create arc shape.
Creates and returns a new UIBezierPath object initialized with an arc of a circle.
For constructing the path
Appends an arc to the receiver’s path.
You can also Refer Core Graphics Tutorial Part 1 With Example and search with arc keyword on browser you will redirect to arc shape on the page.
Another image posted by #Jack in above comments and asked help, here is the image:
I couldn't answer him as comment, so pasting my playground here instead.
let shapeView = UIView(frame: CGRect(x: 0, y: 0, width: 360, height: 60))
let shapeSize = shapeView.frame.size
shapeView.backgroundColor = UIColor.white
let path = CGMutablePath()
path.move(to: CGPoint.zero)
let curveWidthOneFourth = shapeSize.width / 4
let curveHeight = shapeSize.height * 0.2
path.addCurve(to: CGPoint(x: curveWidthOneFourth * 2, y: curveHeight), control1: .zero, control2: CGPoint(x: curveWidthOneFourth, y: curveHeight))
path.addCurve(to: CGPoint(x: shapeSize.width, y: 0), control1: CGPoint(x: curveWidthOneFourth * 3, y: curveHeight), control2: CGPoint(x: shapeSize.width, y: 0))
path.addLine(to: CGPoint(x: shapeSize.width, y: shapeSize.height))
path.addLine(to: CGPoint(x: 0, y: shapeSize.height))
path.addLine(to: CGPoint.zero)
And add our path to a view's layer:
let layer = CAShapeLayer()
layer.path = path
layer.fillColor = UIColor.red.cgColor
shapeView.layer.addSublayer(layer)
Encountered a similar task today, I used CGMutableShape instead of UIBezierPath because I was required to animation the shape.
Anyways, to understand the shape I divided the shape into 6 parts (lines & curves):
Base UIView to draw our shape in
let shapeView = UIView(frame: CGRect(x: 16, y: 16, width: 100, height: 44))
shapeView.backgroundColor = UIColor.lightGray
self.view.addSubview(shapeView)
Actual shape and its path
let path = CGMutablePath()
path.move(to: CGPoint(x: 10, y: 0)) // Move path to beginning of part-1
// Draw above divided parts
path.addLine(to: CGPoint(x: 90, y: 0)) // Part-1
path.addCurve(to: CGPoint(x: 95, y: 22), control1: CGPoint(x: 93, y: 6.6), control2: CGPoint(x: 95, y: 14)) // Part-2
path.addCurve(to: CGPoint(x: 90, y: 44), control1: CGPoint(x: 95, y: 30), control2: CGPoint(x: 93, y: 37)) // Part-3
path.addLine(to: CGPoint(x: 10, y: 44)) // Part-4
path.addCurve(to: CGPoint(x: 5, y: 22), control1: CGPoint(x: 7, y: 37), control2: CGPoint(x: 5, y: 30)) // Part-5
path.addCurve(to: CGPoint(x: 10, y: 0), control1: CGPoint(x: 5, y: 14), control2: CGPoint(x: 6, y: 6.6)) // Part-6
let arcLayer = CAShapeLayer()
arcLayer.path = path
arcLayer.fillColor = UIColor.gray.cgColor
shapeView.layer.insertSublayer(arcLayer, at: 0)

Resources