We’ve arrived at day 43 of the 100 Days of SwiftUI! Yesterday, we wrapped up our Moonshot app and completed a few challenges. Today, we’re diving into the third technique project, where we’ll focus on drawing in SwiftUI. Let’s dive in!
Using paths and shapes in SwiftUI
SwiftUI gives us a dedicated Path
type for drawing custom shapes. Just like colors, gradients, and shapes, paths are views in their own right. This means we can use them just like text views and images.
//
// ContentView.swift
// Drawing
//
// Created by Darryl Soerdjpal on 08/08/2022.
//
import SwiftUI
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) // min X is the smallest x value in the rectangle, max the greatest value and mid is the horizontal halfway point
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
return path
}
}
struct Arc: Shape {
let startAngle: Angle
let endAngle: Angle
let clockwise: Bool
func path(in rect: CGRect) -> Path {
let rotationAdjustment = Angle.degrees(90)
let modifiedStart = startAngle - rotationAdjustment
let modifiedEnd = endAngle - rotationAdjustment
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: modifiedStart, endAngle: modifiedEnd, clockwise: !clockwise)
return path
}
}
struct ContentView: View {
var body: some View {
// Triangle()
// .fill(.red)
// .frame(width: 300, height: 300)
Arc(startAngle: .degrees(0), endAngle: .degrees(110), clockwise: true)
.stroke(.blue, lineWidth: 10)
.frame(width: 300, height: 300)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Adding strokeBorder() support with InsettableShape
When you use the .stroke()
modifier, SwiftUI will draw a outline around the shape. With .strokeBorder()
, the stroke will be drawn on the inside. As for an InsettableShape
:
There is a small but important difference between SwiftUI’s
Hacking with Swift, Paul Hudson (@twostraws)Circle
and ourArc
: both conform to theShape
protocol, butCircle
also conforms to a second protocol calledInsettableShape
. This is a shape that can be inset – reduced inwards – by a certain amount to produce another shape. The inset shape it produces can be any other kind of insettable shape, but realistically it should be the same shape just in a smaller rectangle.
//
// ContentView.swift
// Drawing
//
import SwiftUI
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) // min X is the smallest x value in the rectangle, max the greatest value and mid is the horizontal halfway point
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
return path
}
}
struct Arc: InsettableShape { // InsettableShape inherits from the Shape protocol
let startAngle: Angle
let endAngle: Angle
let clockwise: Bool
var insetAmount = 0.0
func path(in rect: CGRect) -> Path {
let rotationAdjustment = Angle.degrees(90)
let modifiedStart = startAngle - rotationAdjustment
let modifiedEnd = endAngle - rotationAdjustment
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2 - insetAmount, startAngle: modifiedStart, endAngle: modifiedEnd, clockwise: !clockwise)
return path
}
func inset(by amount: CGFloat) -> some InsettableShape {
var arc = self
arc.insetAmount += amount
return arc
}
}
struct ContentView: View {
var body: some View {
// Triangle()
// .fill(.red)
// .frame(width: 300, height: 300)
//
Arc(startAngle: .degrees(-90), endAngle: .degrees(90), clockwise: true)
.strokeBorder(.indigo, lineWidth: 40)
// Circle()
// .strokeBorder(.indigo, lineWidth: 40) // SwiftUI will stroke the inside of the border when using strokeBorder()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Wrap up
And that’s it for day 43! Tomorrow, we’ll dive further into the drawing functions of SwiftUI, so stay tuned for that. Until then!
100 Days of SwiftUI – Day 43