PencilKit. Protocol Oriented Programming. Swift.

Yoel Lev
4 min readSep 20, 2019

--

In this tutorial we’re going to implement PencilKit using Protocol Oriented Programming.

Apple made it very easy for us developers to integrate PencilKit into our project, but I wanted to take it one step further.

First let’s take a brief look at the main Classes that PencilKit uses.

PKCanvasView — A view that captures Apple Pencil input and displays the rendered results in an iOS app.

PKInkingTool — An object that defines the drawing characteristics (width, colour, pen style) to use when drawing lines on a canvas view.

PKToolPicker — An object that displays a selection of drawing tools and colours from which to select in the current window.

PKDrawing — An opaque data object that contains the drawing information captured by a canvas view, which I will not be covering in this article.

The tool picker has three different pen styles: Pen, Marker and Pencil, and this is how they look like.

The Fast Way (Without the Picker Tool)

If you want to have a canvas with just a single tool, You can achieve this by using the following three lines of code.

let canvas = PKCanvasView(frame: bounds)
view.addSubview(canvas)
canvas.tool = PKInkingTool(.pen, color: .black, width: 30)

Let’s get started

  1. Create a brand new Xcode 11 project and pick the Single View Application template.
  2. Name it and save it. (make sure to select the Storyboard as the User Interface instead of SwiftUI)
  3. Rename ViewController to PencilKitViewController using the refactor option Xcode provides.
  4. Create a new file and name it PKCanvas.

Copy the following gist to your project.

Lets start by implementing setupPencilKitCanvas( )

private func setupPencilKitCanvas() {
//1
canvasView = PKCanvasView(frame:self.bounds)
//2
canvasView.delegate = self
//3
canvasView.alwaysBounceVertical = false
//4
canvasView.allowsFingerDrawing = true
//5
canvasView.becomeFirstResponder()
//6
addSubview(canvasView)
//7
if
let window = UIApplication.shared.windows.last, let toolPicker = PKToolPicker.shared(for: window) {
//8
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
toolPicker.addObserver(self)
canvasView.becomeFirstResponder()
}
}
  1. We start by creating a PKCanvasView using our views bounds.
  2. PencilKit has optional delegates we can conform to which are PKCanvasViewDelegate and PKToolPickerObserver. We will be implementing those methods so we can determine the start and end of the drawn content events.
  3. We currently do not use any scrolling view so we disable the bounce effect.
  4. A Boolean value that indicates whether the canvas accepts input from the user’s finger in addition to Apple Pencil.
  5. Make the CanvasView first in the view hierarchy.
  6. Finally add the PencilKit Canvas to our view.

Adding the Tool Picker

7. Here we extract our top window in order to display the PencilKit Tool picker above it. We then create a ToolPicker and pass our window to it.

8.We then set the visibility for the tool picker, based on when the specified responder object becomes active, which is our canvasView. Finally we set the tool picker observers and delegate to self.

Add SetupPencilKitCanvas we just implemented to the init(frame: CGRect) in PKCanvas class.

//PKCanvas.swift//MARK: - iOS Life Cycleoverride init(frame: CGRect) {
super.init(frame: frame)

setupPencilKitCanvas()
}

Update CanvasOrientation

func updateCanvasOrientation(with frame: CGRect) {
//1. assign updated frame to canvas
self.canvasView.frame = frame
//2.assign updated frame to self view
self
.frame = frame
}

PencilKit Interface

Create a new Swift file and name it PencilKitInterface. Copy the following gist

Create PencilKit Canvas

In the PencilKitInterface file scroll down to createPencilKitCanvas implementation which is in the PencilKitInterface extension.

createPencilKitCanvas receives two parameters first the frame and a delegate, as the second which is PencilKitDelegate. I have added and with this we will be able to save a snapshots of our drawings ( This will be covered in the second part of this tutorial).

Add the following to createPencilKitCanvas function.

func createPencilKitCanvas(frame: CGRect, delegate: PencilKitDelegate) -> PKCanvas {

//1. Assign PKCanvas to our interface property
pencilKitCanvas = PKCanvas(frame: frame)
//2. Connect the delegates
pencilKitCanvas.pencilKitDelegate = delegate

//3.
return
pencilKitCanvas
}

Update Orientation

Finally, add the implementation to updateCanvasOrientation(with frame: CGRect) which will be called when the device orientation changes.

func updateCanvasOrientation(with frame: CGRect){
//1.
pencilKitCanvas.updateCanvasOrientation(with: frame)
}

Let’s add it to our PencilKit ViewController

  1. We create and instantiate a PKCanvas variable.
  2. We extend our ViewController to conform to both PencilKitInterface & PencilKitDelegate.
  3. We then addPencilKit() function which will return a PKCanvas instance that will be assigned to pencilKitCanvas and add it to the view.
  4. Every time the device orientation changes viewDidLayoutSubview will be called triggering updateCanvasOrientation.
  5. The Moment you've been waiting for is here, addPencilKit() is called when viewDidAppear is called .

Build and Run !

--

--

Yoel Lev

Senior iOS Developer @ TinyTap, Creator of Wavez. #wavez_app