skip to content
Law

Learning to draw with SwiftUI

/ 3 min read

Table of Contents

I have zero knowledge in drawing shapes using SwiftUI. As a weekend practice session, I will try to learn how to draw an arrow.

After a few quick readings and research, I am going to use SwiftUI’s Path API. Apple’s Path tutorial is not helping though, it’s just too advance right off the bat. Why not start with a straight line?

Straight Line

Let’s draw a straight line in red.

import SwiftUI
struct MainView: View {
var body: some View {
NavigationStack {
Path { path in
path.move(to: CGPoint(x: 100, y: 120))
path.addLine(to: CGPoint(x: 100, y: 400))
path.closeSubpath()
}
.stroke(Color(.red), lineWidth: 3)
}
}
}

So far so good. Now, we need to center the line. Let’s wrap it in GeometryReader.

import SwiftUI
struct MainView: View {
var body: some View {
NavigationStack {
GeometryReader { geometry in
Path { path in
path.move(to: CGPoint(x: geometry.size.width / 2, y: 120))
path.addLine(to: CGPoint(x: geometry.size.width / 2, y: 400))
path.closeSubpath()
}
.stroke(Color(.red), lineWidth: 3)
}
}
}
}
Screenshot 2025-03-03 at 10

Triangle

Next, we need a triangle on top of the line. After some trial and error, I managed to form an awkward triangle. More work to be done.

import SwiftUI
struct LaunchView: View {
var body: some View {
NavigationStack {
GeometryReader { geometry in
Path { path in
path.move(to: CGPoint(x: geometry.size.width / 2, y: 120))
path.addLine(to: CGPoint(x: geometry.size.width / 3, y: 150))
path.addLine(to: CGPoint(x: 300, y: 150))
path.closeSubpath()
}
.fill(Color(.red))
Path { path in
path.move(to: CGPoint(x: geometry.size.width / 2, y: 120))
path.addLine(to: CGPoint(x: geometry.size.width / 2, y: 400))
path.closeSubpath()
}
.stroke(Color(.red), lineWidth: 3)
}
}
}
}
Screenshot 2025-03-03 at 10

Figure out the width

I know what’s wrong with my triangle, the width is not consistent. Or the x value between the lines needs to be equal. Let’s compute the geometry width and add 20 to one line, and subtract 20 to another line.

import SwiftUI
struct LaunchView: View {
var body: some View {
NavigationStack {
GeometryReader { geometry in
Path { path in
path.move(to: CGPoint(x: geometry.size.width / 2, y: 120))
path.addLine(to: CGPoint(x: geometry.size.width / 2 + 20, y: 140))
path.addLine(to: CGPoint(x: geometry.size.width / 2 - 20, y: 140))
path.closeSubpath()
}
.fill(Color(.red))
Path { path in
path.move(to: CGPoint(x: geometry.size.width / 2, y: 120))
path.addLine(to: CGPoint(x: geometry.size.width / 2, y: 400))
path.closeSubpath()
}
.stroke(Color(.red), lineWidth: 3)
}
}
}
}

Boom! It worked!

Screenshot 2025-03-03 at 10

I guess the final task is to group the 2 path objects together. If it is even possible. I guess I am just going to create a separate view, and call it ArrowView.

import SwiftUI
struct ArrowView: View {
var body: some View {
GeometryReader { geometry in
Path { path in
path.move(to: CGPoint(x: geometry.size.width / 2, y: 120))
path.addLine(to: CGPoint(x: geometry.size.width / 2 + 20, y: 140))
path.addLine(to: CGPoint(x: geometry.size.width / 2 - 20, y: 140))
path.closeSubpath()
}
.fill(Color(.red))
Path { path in
path.move(to: CGPoint(x: geometry.size.width / 2, y: 120))
path.addLine(to: CGPoint(x: geometry.size.width / 2, y: 400))
path.closeSubpath()
}
.stroke(Color(.red), lineWidth: 3)
}
}
}

In this way, I can reuse it like this.

import SwiftUI
struct MainView: View {
var body: some View {
NavigationStack {
ArrowView()
}
}
}