Introducing PencilKit in iOS 13

WWDC 2019 introduced another interesting framework, PencilKit for iOS 13. Apple introduces this drawing and annotation framework to make it easier for developers to integrate it into their own apps.

ios-pencilkit-ipad-demo

The goal of this article is to walk through the PencilKit architecture and implement it in our iOS Application.

Note: PencilKit requires Xcode 11 and iOS 13

PencilKit : Inside the Wood

PKCanvasView is the region inside which drawing is possible. The user can draw all their art and doodles here.
PKCanvasView allows panning and zooming since it’s essentially a part of the UIScrollView.
PKDrawing is our data model which is responsible for storing all the drawings.
PKCanvasView has a drawing property that is used to get and set the data model.
PKToolPicker is the floating UI which consists of all the brushes, color palletes and helper tools for the drawing.
PKTools holds the different kinds of brushes which are essentially subclasses:
PKEraserTool – This requires specifying either vector or bitmap as the type. Accordingly, vector object or pixels can be erased from the screen.
PKInkingTool – These can be either among pen, marker or pencil.
PKLassoTool is a selection tool for selecting a drawing area. It pops up a context menu that allows copy/paste and duplicating the selected drawing.

Here’s a look at lasso tool in action in an iOS Device

ios-pencilkit-lasso-tool

The ToolPicker is floating in regular-size classes (iPad devices) but is fixed at the bottom in the compact size classes. Besides, the undo/redo and minimize buttons are not available in the compact version of Toolpicker.

The ToolPicker palette has a responder based visibility. That means that it’s visible when the PKCanvasView is the firstResponder and can be hidden by resigning PKCanvasView from first responder.

To enable/disable finger drawing with PencilKit we can set the boolean property allowsFingerDrawing on the PKCanvasView.

Enough talk, let’s build our iOS Application using PencilKit now.

Implementation

We’ll be implementing the following things in our iOS Application.

  • Adding Undo, Redo Draw Actions for iOS Devices.
  • Saving drawing sketches to the photos library.

Set Privacy – Photo Library Additions Usage Description to Allowed in Info.plist in order to save images.

Our Storyboard

ios-pencilkit-storyboard

For the undo and redo actions we’ve used the built-in selectors.

Setting The Canvas View

Following code sets the Canvas View for you:

let canvasView = PKCanvasView(frame: .zero)
canvasView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(canvasView)
NSLayoutConstraint.activate([
            canvasView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
            canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            canvasView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            canvasView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        ])

Setting The ToolPicker

  • Initialisation is done by asking for the shared Tool Picker for the window.
  • Canvas View is added as an observer.
  • setVisible true indicates that whenever the Canvas View becomes the first responder, the palette will show up.
guard
            let window = view.window,
            let toolPicker = PKToolPicker.shared(for: window) else { return }

        toolPicker.setVisible(true, forFirstResponder: canvasView)
        toolPicker.addObserver(canvasView)
        canvasView.becomeFirstResponder()

Clear the CanvasView

In order to clear the PKCanvasView just reinitialize the PKDrawing instance.

canvasView.drawing = PKDrawing()

Saving Drawing To UIImage

func image(from rect: CGRect, scale: CGFloat) is invoked on the PKDrawing instance.
It returns a UIImage of the portion of the drawing we want to capture.
The scale is typically set to 1.0. Set higher values for a more detailed image(useful for retina display).

let image = canvasView.drawing.image(from: canvasView.drawing.bounds, scale: 1.0)
UIImageWriteToSavedPhotosAlbum(image, self, nil, nil)

Here’s the full source code of our PencilKit iOS 13 Application:

import UIKit
import PencilKit

class ViewController: UIViewController {
    
    let canvasView = PKCanvasView(frame: .zero)
    @IBOutlet weak var navigationBar: UINavigationBar!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setNavigationBar()
        
        canvasView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(canvasView)
        
        NSLayoutConstraint.activate([
            canvasView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
            canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            canvasView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            canvasView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        ])
    }
    
    func setNavigationBar() {
        if let navItem = navigationBar.topItem{
            let saveItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveImage))
            
            let clearItem = UIBarButtonItem(title: "Clear", style: .plain, target: self, action: #selector(clear))
            
            let toggleItem = UIBarButtonItem(title: "Toggle Picker", style: .plain, target: self, action: #selector(togglePicker))

            navItem.rightBarButtonItems?.append(clearItem)
            navItem.rightBarButtonItems?.append(toggleItem)
            navItem.rightBarButtonItems?.append(saveItem)
        }
        
    }

    @objc func saveImage() {
        let image = canvasView.drawing.image(from: canvasView.drawing.bounds, scale: 1.0)
        UIImageWriteToSavedPhotosAlbum(image, self, nil, nil)
    }
    
    @objc func clear() {
        canvasView.drawing = PKDrawing()
    }
    
    @objc func togglePicker() {
        if canvasView.isFirstResponder{
            canvasView.resignFirstResponder()
        }else{
            canvasView.becomeFirstResponder()
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        guard
            let window = view.window,
            let toolPicker = PKToolPicker.shared(for: window) else { return }

        toolPicker.setVisible(true, forFirstResponder: canvasView)
        toolPicker.addObserver(canvasView)
        canvasView.becomeFirstResponder()
    }
}

ios-pencilkit-final-output

That sums up this introductory article on PencilKit in iOS 13. Now go draw and doodle with it.
You can download the full source code from our Github Repository.

Leave a Reply

Your email address will not be published. Required fields are marked *