# Custom Shapes in SwiftUI

Hi everyone! In [the post yesterday](https://xavier7t.com/swiftui-shapes), we covered the standard SwiftUI shapes - rectangle, rounded rectangle, circle, ellipse, and capsule and discussed how to style these shapes with view modifiers. Apartment from these shapes, there’s one more `Shape` structure in SwiftUI called `Path`, which can be used to create custom shapes for more interesting user interfaces. Today, we’ll going to take a look at how to use `Path` in SwiftUI.

The code in this post is available [here](https://github.com/xavier7t/iOSDevX/tree/main/iOSDevX/202303-Mar%202023/Custom%20Shape).

# Overview

What is `Path`? `Path` is a public struct in SwiftUI for custom 2D shape creation. There’re multiple ways to create a `Path` and for this post, we’ll focus on a commonly used initializer: `init(_ callback: (inout Path) -> ())`.

The `inout` keyword makes the Path mutable in the closure, therefore in a SwiftUI view, we can use the pattern below to create a path.

```swift
Path { path in
    //define or configure the path here
}
```

# Coordinate in iOS

Before getting started, it’s necessary to take a look at the coordinate system in iOS. Below is a comparison of the mathematical coordinate and the one in iOS. The biggest difference is that the point of origin (0,0) is located at the upper left corner of a screen (or a frame), and going downward will increase the value on the y-axis.

In Swift, the location of a point can be represented by `CGPoint` with its x and y values. For instance, point (2,1) can be written as `CGPoint(x: 2, y: 1)`. Note that both of the x and y values are CGFloat type instead of a double or integer.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678246677408/91286988-cafc-4927-82dd-ac4aaedaaf84.png align="center")

`Path` basically means the path of movement of a point. And a shape can be composed of one or multiple paths.

Now we’re ready to move forward. So let’s take a look at how to create shapes with functions in `Path`.

# `move` and `addLine`

The function `move(to: CGPoint)` is often used to define the starting point of a shape, by going to the point `to` to start drawing. And `addLine(to: CGPoint)` connects the currently moved-to point and the `to` point and forms a straight line.

Let’s look at the example below, where a triangle is drawn.

```swift
Path { path in
    path.move(to: CGPoint(x: 150, y: 0))
    path.addLine(to: CGPoint(x: 10, y: 100))
    path.addLine(to: CGPoint(x: 150, y: 100))
    path.addLine(to: CGPoint(x: 150, y: 0)) 
    // or path.closeSubpath() //for the last line closing the shape
}
.stroke()
```

The `.move` function set the starting point at (150, 0). And the three `addLine` functions added three straight lines, as shown in a coordinate below. And finally, the `stroke` modifier made the shape bordered with no fill color.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678247873475/408d80f5-6a7a-42ab-bd7e-0436e5db0038.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678248192151/3491e31a-5bba-4536-991d-a8aba73cdfaa.png align="center")

# addLines

It might be easy to right `addLine` function to create a shape like a triangle, since there are only three lines required. What if we need a trapezoid?

In such a case, we can use the function `addLines` and pass in an array of CGPoints, including the starting point.

```swift
Path { path in
    path.addLines([
        .init(x: 50, y: 0),
        .init(x: 150, y: 0),
        .init(x: 150, y: 100),
        .init(x: 0, y: 100)
    ])
}
```

The coordinate below shows the position of these points. And the screenshot after it shows how the shape looks like in the preview canvas.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678248564890/ef48f120-de5c-4848-bab2-671440b78dda.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678248627046/815a1c6f-4320-4dc6-ba82-6f8a75fe18c4.png align="center")

# addArc

Now let’s see how to create an arc shape. Before looking into the code, I’d like to introduce the angle and degrees in iOS too. (Also applicable to some other languages.)

1. The clockwise direction is the opposite direction of the natural clockwise direction;
    
2. 0-degree starts from the right hand side (basically the x-axis).
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678249871487/92d56ab4-eabe-4b00-b0bb-c3ab2b71cb71.png align="center")

Now let’s take a look at the code below. It declared a path and called the addArc function. This function requires the following parameters:

1. center: CGPoint -&gt; to determine the location of the arc
    
2. radius: CGFloat -&gt; to determine the size of the arc
    
3. start angle: Angle
    
4. end angle: Angle
    
5. clockwise: Bool
    

The last three parameters might be tricky as the value might not represent in a natural way. So in a mathematical language, the arc created by this code snippet starts at 90 degrees and ends at 270 degrees, and the shape is drawn in the counterclockwise direction.

Then the modifiers applied made it a stroke, with a linear gradient fill color.

```swift
Path { path in
    path.addArc(center: .init(x: 100, y: 50), radius: 50, startAngle: .init(degrees: 0), endAngle: .init(degrees: 90), clockwise: true)
}
.stroke(lineWidth: 5)
.fill(LinearGradient(colors: [.indigo, .indigo, .indigo, .green, .yellow], startPoint: .topLeading, endPoint: .bottom))
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678250295464/5079ab2e-d86a-48f0-b04e-62a10d1008ca.png align="center")

# addCurve

Finally, let’s look at the `addCurve` function. To create a curve, we also need to specify a starting point, so this function is often used together with `move(to: CGPoint)`.

The `addCurve` have three parameters:

* to: CGPoint -&gt; the ending point of the curve
    
* control1: CGPoint
    
* control2: CGPoint
    

Both of the controls are CGPoints, and they are used to control the direction of the curve path (the controls are also called apex points).

Let’s take a look at the code below. This block of code creates a path with a downward curve. Since the controlling points are the same, the curve looks symmetric. (as the x value `125` is in the middle, (50 + 200) / 2 = 250.)

```swift
Path { path in
    path.move(to: .init(x: 50, y: 0))
    path.addCurve(to: .init(x: 200, y: 00), control1: .init(x: 125, y: 60), control2: .init(x: 125, y: 60))
}
.stroke(lineWidth: 8)
.foregroundColor(.indigo)
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678250799327/504d81b1-cbd3-4a14-99fa-9b1d84a2787d.png align="center")

If you change the controlling point’s value you can find that the curve will move toward to the point with a higher absolute x or y value, and the negative/positive symbol determines the direction.

```swift
        Path { path in
            path.move(to: .init(x: 50, y: 0))
            path.addCurve(to: .init(x: 200, y: 00), control1: .init(x: 125, y: -120), control2: .init(x: 125, y: 160))
        }
        .stroke(lineWidth: 8)
        .foregroundColor(.indigo)
```

For instance, if we change the first controlling point to (125, -120), the left side of the curve will move upward.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678251196346/c36813ad-2ce0-48fd-b0d4-020e8eb0c08d.png align="center")

That’s all for the custom shapes. I hope you learned something and will be able to create various of desired shapes in your project.

If you find this post helpful, please hit the like button or leave a comment. Also remember to subscribe to my newsletter if you’d like to receive more posts like this via email.

I’ll see you all in the next post!
