Using Calculated Geometric Properties: How To Draw Complex Shapes in SwiftUI

Using Calculated Geometric Properties: How To Draw Complex Shapes in SwiftUI

The path command in SwiftUI is nothing more than the pen or vector tool in Figma and Sketch. You can grab it to draw any shape you could imagine. Drawing basic shapes with a straight point in SwiftUI is easy but it becomes mind-bending when you need to sketch complex shapes consisting of straight and curved points using fixed geometric values. Luckily, the Core Graphics Framework makes it possible to use calculated geometric properties (minX, midX, maxX, minY, midY, maxY) of a rectangle to make the process easier.

This article is the first part of a series of articles that cover how to draw custom shape outlines with SwiftUI. In this article, you will discover how to construct basic and complex 2D shape outlines in SwiftUI using minimum, medium and maximum coordinates of rectangles. Using these values removes the pain of guessing fixed values to draw shapes. The techniques, tips, and tricks you learn from this post will assist you in drawing complex shapes in SwiftUI with your eyes closed.

It may sound like a brag, but continue reading to find out how to draw the Stream logo mark in SwiftUI. Grab the code from GitHub.

No alt text provided for this image

What Are Calculated Geometric Properties?

Calculated geometric properties in Core Graphics allow you to specify the size (width and height) and origin of a 2D outline using minimum, medium, and maximum values. This removes the tedious work of using fixed values for specifying origin and size when drawing custom shapes.

Understanding the iOS Coordinate System

No alt text provided for this image

Traditionally, the origin of a rectangle is positioned at the bottom left in most systems like macOS. iOS, on the other hand, has a flipped geometry. So the origin is situated at the top-left corner of a rectangle (minX, minY). Having this at the back of your mind, you can draw custom shapes starting from the origin in the clockwise or anticlockwise directions on iOS. 

Launch Xcode and Create a Blank SwiftUI Project

Create a new SwiftUI app in Xcode and choose any name you prefer. This demo project uses the product name CalculatedGeometricProperties.

Using an Image As Drawing Template

No alt text provided for this image

To sketch the Stream logo, you will use the (minX, midX, maxX, minY, midY, maxY) properties of a rectangle instead of fixed values. This makes it comprehensive to create custom shapes. To understand how this works visually, you will add the SVG image template above to the Xcode assets library and pull it to the content view to guide you draw the shape easily. 

How to Draw Custom Shapes in SwiftUI

You can create a path in SwiftUI in several ways. Let's add it as a function that accepts CGRect (origin: (x: 0.0, y: 0.0), size(width: 1.0, height: 1.0)) and returns a path. In this way, you will have more customization options.

  1. Create a struct called StreamLogo. It must conform to the Shape protocol.

struct StreamLogo: Shape { }        

2. Add the variable var controlPoint: Double inside the struct.

3. Add the method path that takes CGRect and returns a path.


func path(in rect: CGRect) -> Path { var pencil = Path() return pencil }        

4. Inside this method, you should create an instance of the path var pencil = Path(). You can think of this as drawing on physical paper. To draw on paper, for example, you need a pen or pencil.

5. In the ContentView struct, add the variable var controlPoint = 0.0 to the declaration section. In the body of the content view, add a ZStack to contain the drawing template cgrect-min-mid-max and an instance of the custom shape.



ZStack {

   Image("cgrect-min-mid-max")
       .opacity(0.25)
            
   StreamLogo(controlPoint: controlPoint)
        .stroke(.blue, style: StrokeStyle(lineWidth: 10, 
            lineCap: .round, lineJoin: .round))
        .offset(x: 20, y: 16)
  }        

When you draw the shape, it adds the fill by default which may not be what you want. So, add the stroke modifier to remove the fill. Also, iOS draws the shape at the top-left corner of the screen. Use the offset modifier to move it away from the top-left corner. An ideal option is to use Geometry Reader.

6. Now, you have Path(), the paper to draw on, and the instance of the path (pencil) to draw. You can sketch the logo starting from anywhere on the drawing template. To simplify the process, you should begin from the top-left (minX, minY) and move in a clockwise direction.

7. Move to the origin to start drawing pencil.move(to: CGPoint(x: rect.minX, y: rect.minY)). Draw the first point at the origin (minX, minY) using pencil.addLine(to: CGPoint(x: rect.minX, y: rect.minY)).

8. Move in a clockwise direction toward midX and specify the second point pencil.addLine(to: CGPoint(x: rect.midX - 20, y: rect.minY + 8)). Since this point is on the left of midX, you can deduct 20 from midX. On the y-axis, positive values go down and negative values go up. So, add 8 to minY to move the line down toward the right.

No alt text provided for this image

9. The next line must move up toward midX. Add the line pencil.addLine(to: CGPoint(x: rect.midX, y: rect.minY - 15)). At this point, subtracting 15 from minY makes the line moves up.

No alt text provided for this image

10. Draw the next line to be opposite of the previous pencil.addLine(to: CGPoint(x: rect.midX + 20, y: rect.minY + 8)).

No alt text provided for this image

11. The line that follows is the opposite of the first line from the origin. Add pencil.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)).

No alt text provided for this image

12. Draw a line from the previous one and downward using pencil.addLine(to: CGPoint(x: rect.maxX - 40, y: rect.maxY)). You should subtract 40 from maxX to make the line point toward the left.

No alt text provided for this image


13. Draw a horizontal line pencil.addLine(to: CGPoint(x: rect.minX + 40, y: rect.maxY)) and add 40 to minX so that the left and right sides of the figure become the same.

No alt text provided for this image

14. Finally, you should close the path. You can close it in two ways. In the first option, you can copy and paste the first control point below the last line path.addLine(to: CGPoint(x: rect.minX, y: rect.minY)). In the second option, you can use the method pencil.closeSubpath().

No alt text provided for this image

15. Apply the frame modifier to change the height of the shape .frame(width: 184, height: 80) in the content view.

No alt text provided for this image

16. Remove the image template Image("cgrect-min-mid-max") and increase the line width of the shape.

No alt text provided for this image
Easy, huh?


Putting All Together

Replace the content of ContentView.swift with the sample code below.



//  ContentView.swift

//  CalculatedGeometricProperties

//  Created by amosGyamfi from getstream.io

//  How to draw the Stream logo using the calculated CGRect 
    geometric properties minimum, medium, and maximum


import SwiftUI


struct StreamLogo: Shape {

    var controlPoint: Double

    

    func path(in rect: CGRect) -> Path {

        var pencil = Path()

        

        // INSTRUCTIONS

        // 1. Move to an origin

        pencil.move(to: CGPoint(x: rect.minX, y: rect.minY))

        pencil.addLine(to: CGPoint(x: rect.minX, y: rect.minY))

        // 2. Move horizontally downward

        pencil.addLine(to: CGPoint(x: rect.midX - 20, y: rect.minY + 
        8))

        // 3.Move upward at an angle towards midX

        pencil.addLine(to: CGPoint(x: rect.midX, y: rect.minY - 15))

        // 4. Opposite of the previous line

        pencil.addLine(to: CGPoint(x: rect.midX + 20, y: rect.minY + 
        8))

        // 5. Opposite of the first line

        pencil.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))

        // 6. Move downwards

        pencil.addLine(to: CGPoint(x: rect.maxX - 40, y: rect.maxY))

        // Move horizontally

        pencil.addLine(to: CGPoint(x: rect.minX + 40, y: rect.maxY))

        // Close the path

        // Option 1

        //path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))

        // Option 2

        pencil.closeSubpath()

        

        return pencil

    }

}


struct ContentView: View {

    var controlPoint = 0.0

    

    var body: some View {

        StreamLogo(controlPoint: controlPoint)

            .stroke(.blue, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))

            .frame(width: 184, height: 80)

            .offset(x: 20, y: 16)

    }

    

}



struct ContentView_Previews: PreviewProvider {

    static var previews: some View {

        ContentView()

    }

}        

What Next?

To learn more about drawing custom shape outlines in SwiftUI, check out the Stream Developers YouTube Channel. You may subscribe to SwiftUI Tutorial#1 on YouTube. Download the sample code as a gist on GitHub. Enjoy!!!.

Craig Clayton

Senior Software UI Engineer (iOS/macOS) | YouTube Content Creator | Author

2y

Great job man!!!

Vaibhav Thakur

Software Developer @ Globant | SDE-1 | LangChain | RAG Architecture | Prompt Engineering | GCP | PICT'21

2y

Amos Gyamfi How can we draw curve shapes using path and CGRect?

To view or add a comment, sign in

More articles by Amos Gyamfi

Insights from the community

Others also viewed

Explore topics