Supporting SwiftUI with Core Graphics

Nov 22 2022 · Swift 5.5, iOS 15, Xcode 13

Part 1: Supporting SwiftUI with Core Graphics

04. Build a UIKit Drawing Pad

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 03. Host a UIView in a SwiftUI View Next episode: 05. Drawing with Pencil

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 04. Build a UIKit Drawing Pad

CanvasView.swift

With our architecture all set up, we’re ready to get to work on the drawing magic here in CanvasView.swift. We’ll use a method called touchesMoved. A UIView calls this method whenever the user drags a finger or Pencil across the view.

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
  guard let touch = touches.first else { return }
}

We’re given a set of touches, in case the user has used several fingers, but in this case we only want the first one. Instead of saving a path, like in the SwiftUI drawing pad, we’re going to draw a stroke directly into the drawingImage. Set up the image with UIGraphicsImageRenderer.

drawingImage = UIGraphicsImageRenderer(size: bounds.size).image { context in

}

We use the view’s bounds as the size of the image. Fill the context with a white background and draw the current drawing image into the context.

UIColor.white.setFill()
context.fill(bounds)
drawingImage?.draw(in: bounds)

With the current drawing image in the context, we’re ready to draw the new strokeon top of it. We’ll do this in a new method.

private func drawStroke(context: CGContext, touch: UITouch) {
  
}

We get the previous touch location and the current touch location.

let previousLocation = touch.previousLocation(in: self)
let location = touch.location(in: self)

Set up our line properties for this context.

let lineWidth: CGFloat = 5
context.setLineWidth(lineWidth)
context.setLineCap(.round)
color.setStroke()

We make the line cap round so that the stroke extends a bit further and joins the ends between each stroke properly. And then wrap it up by drawing a new line between the two touch points.

context.move(to: previousLocation)
context.addLine(to: location)
context.strokePath()

Call this method from the image renderer in touchesMoved.

drawStroke(context: context.cgContext, touch: touch)

We’ve created the drawing image from the strokes. We now need to draw the image into the view. We do that with the UIView’s draw method. I’ll put this above touchesEnded.

override func draw(_ rect: CGRect) {
  drawingImage?.draw(in: rect)
}

We should never call this method directly. CanvasView will automatically call this method whenever the view loads or appears from behind another view. But as we’ll see in a moment, we need CanvasView to call this method when the drawing image changes.

DrawingPadView.swift

Open DrawingPadView.swift to check out our drawing so far. If we preview this and draw, nothing happens. The view isn’t calling that draw method when the drawing image changes.

CanvasView.swift

Go back CanvasView and at the end of touchesMoved, call setNeedsDisplay.

setNeedsDisplay()

This marks the view as needing to be redrawn.

DrawingPadView.swift

Go back to DrawingPadView.swift and try it out again in the preview. You may need to clean and build before it works. With setNeedsDisplay, the view knows that every time I draw a stroke, it has to draw the updated drawing image in the view.

We’ve now set up a drawing pad to do basically what the SwiftUI drawing pad did, but using UIKit. With that done, we’ll find it much easier to add Pencil support in the next video.