Freeform Drawing in SwiftUI
Reading time: 2 min
This recipe shows how to let your users draw with mouse/finger in SwiftUI.
The result will look like this:
This recipe is a part of our Road to DigitalSignatureView series, which explores various components vital to functioning of our SwiftUIDigitalSignature component.
You can find the full code of the recipe in this gist.
The recipe goes like this:
- Create a model to hold your drawing data.
- Collect drawing data using
DragGesture
. - Render the data with a custom
Shape
.
OK, so the first step is create a struct to hold your drawing data. It contains the points which the user went through while drawing, as well a breaks that they took when they lifted their finger/mouse up:
struct DrawingPath {
private var points = [CGPoint]()
private var breaks = [Int]()
mutating func addPoint(_ point: CGPoint) {
points.append(point)
}
mutating func addBreak() {
breaks.append(points.count)
}
}
Next, use DragGesture
to obtain drawing data - points in onChanged
and breaks in onEnded
:
struct DrawViewTest: View {
@State private var drawing: DrawingPath
var body: some View {
ZStack {
Color.white // drawing background
// WE'LL ADD THE DRAWING SHAPE HERE
}.gesture(DragGesture()
.onChanged( { value in
drawing.addPoint(value.location)
}).onEnded( { value in
drawing.addBreak()
}))
}
}
Lastly, here's a Shape
that turns DrawingPath
data into a SwiftUI Path
:
extension DrawingPath {
var path: Path {
var path = Path()
guard let firstPoint = points.first else { return path }
path.move(to: firstPoint)
for i in 1..<points.count {
if breaks.contains(i) {
path.move(to: points[i]) // jump after breaks
} else {
path.addLine(to: points[i]) // connect points otherwise
}
}
return path
}
}
struct DrawShape: Shape {
let drawingPath: DrawingPath
func path(in rect: CGRect) -> Path {
drawingPath.path
}
}
Then just update the DrawViewTest
's body
to make use of DrawShape
:
var body: some View {
ZStack {
Color.white // drawing background
DrawShape(drawingPath: drawing)
.stroke(lineWidth: 5) // define stroke width
.foregroundColor(.blue) // define stroke color
// ... the rest as it was before