Supporting SwiftUI with Core Graphics

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

Part 1: Supporting SwiftUI with Core Graphics

05. Drawing with Pencil

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: 04. Build a UIKit Drawing Pad Next episode: 06. Shading 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: 05. Drawing with Pencil

To test the code in this video and the next one, you’ll need an Apple Pencil and a recent iPad that supports it. You can skip this video if you don’t have them, and just use the touch drawing pad for the rest of the course.

In iOS 13 Apple gave us PencilKit so that you can easily incorporate markup and Pencil drawing in your apps. There’s a great WWDC 2019 video called Introducing PencilKit that you can watch.

And you should definitely use PencilKit if you can. But if you want to create a custom experience with your own tools, then you’ll need to dive a bit deeper into how to interact directly with Pencil. That’s what we’ll be covering in these two next episodes.

As opposed to drawing with your finger, using Pencil, you can tell the force or pressure the user’s applying, and also the direction and the tilt.

In this episode, when the user applies more force with Pencil, we’ll make the stroke wider. When they tilt the pencil, instead of drawing a stroke, we’ll draw a shaded effect using a custom pencil texture image.

We’ll first get the force value from Pencil and change the stroke’s lineWidth accordingly. Open CanvasView.swift. We’ll first set up some default parameters for the line width.

private let defaultLineWidth: CGFloat = 6
private let minLineWidth: CGFloat = 5
private let forceSensitivity: CGFloat = 4

You can change forceSensitivity to make the stroke more or less sensitive to pressure. In drawStroke, update the lineWidth.

var lineWidth = defaultLineWidth
if touch.force > 0 {
  lineWidth = touch.force * forceSensitivity
}

If the force in the touch is greater than zero, that means that you’re drawing with Pencil. You can then change the lineWidth depending on the force. To check this, we’ll have to run the app on the actual iPad device.

So, build and run on the iPad and check out your variable pressure. When I draw really fast, the lines aren’t smooth, because we’re drawing straight lines between the drag points.

Coalesced Touches

In the SwiftUI drawing pad, we replaced the stroke after drawing it with a Bezier spline which smoothed out the curve, but Pencil has a clever way of capturing touches that are lost when you drag fast.

UIKit delivers touches to the app at 60 hertz, but when Pencil is near the iPad screen, newer iPads are capable of recording touches at up to 240 hertz.

UIKit coalesces extra touches into a UITouch object, and if we want extra precision, we can retrieve them.

Let’s see how we do that.

Still in CanvasView.swift, locate the image renderer in touchesMoved. This is where we create the image. Instead of calling drawStroke for one touch…

We’ll create an array of touches.

var touches: [UITouch] = []

Find out if there are any coalesced touches - these are the ones missed during a fast drag. If there are, put them all into the array.

if let coalescedTouches = event?.coalescedTouches(for: touch) {
  touches = coalescedTouches
}

You will get coalesced touches whether you’re using your finger or a pencil, but just in case something goes wrong, add the single touch we know we have to the array.

if let coalescedTouches = event?.coalescedTouches(for: touch) {
  touches = coalescedTouches
} 🟩else {
  touches.append(touch)
}
print("Touch count: ", touches.count)

Then we can draw all the touches in the array.

for touch in touches {
  drawStroke(context: context.cgContext, touch: touch)
}

Build and run on the iPad, again, and try this out with fast sweeping curves. The curves are a lot smoother now, and you can see from the print statement, that we’re capturing up to 6 touches with each stroke.

But if I draw with my finger, we’re capturing fewer touches: usually one or two. The drawing is a little more jagged, and of course there’s no variable pressure to change the width of the stroke like I can do with the pencil.