Reading time: 2 min

This recipe shows how to let your users draw with mouse/finger in SwiftUI.

The result will look like this:

ezgif-1-69965200c46e

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:

  1. Create a model to hold your drawing data.
  2. Collect drawing data using DragGesture.
  3. 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

Next Post Previous Post