Active Filters: Apple Pencil

Last week's introduction of the new iPad Pro brought us some fantastic improvements to the Apple Pencil. One of those improvements was the ability for the Apple Pencil to recognize simple gestures. Today we'll look at how to work these in our code. Let's dive in! 🏊‍♀️

First, let's cover the new system-level options for Apple Pencil. If we pair an Apple Pencil with our new iPad Pro by attaching it to the new magnetic inductive charger on the edge, we'll see a new item in our Settings.app for the Pencil.

Apple has provided a way for us to read these settings inside our own apps. This is through the UIPencilInteraction class's preferredTapAction class variable. This returns an enum representing the user's current selection for how the double-tap Pencil gesture should behave.

Apple recommends we make the default behavior of our apps match whatever the user has selected in Settings.app.

We'll begin by setting our view controller as the delegate of a new UIPencilInteraction object, then adding that new interaction object to our view controller's view.

class ViewController: UIViewController, UIPencilInteractionDelegate {
  init() {
    super.init(nibName: nil, bundle: nil)

    let pencilInteraction = UIPencilInteraction()
    pencilInteraction.delegate = self

    view.addInteraction(pencilInteraction)
  }

  // ...
}

Then, we'll implement UIPencilInteractionDelegate's one function: pencilInteractionDidTap(_:):

class ViewController: UIViewController, UIPencilInteractionDelegate {
  // ...

  // UIPencilInteractionDelegate

  public func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
    print("pencil double tapped!")
  }

  // ...
}

That function will be called anytime the user double-taps on their Pencil. Neat!

Next, we'll add some handling for the system setting:

public func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
  switch UIPencilInteraction.preferredTapAction {
    case .ignore: return
    case .showColorPalette: showColorPallet()
    case .switchEraser: change(tool: .eraser)
    case .switchPrevious: changeToPreviousTool()
  }
}

Some apps don't have tools like erasers though, and some apps might not have "tools" at all. In these cases, we can define our own behavior to respond to a double-tap on the Pencil.

In our case we'd like to support both system and custom actions.

We'll add a preference (stored in UserDefaults) to our app for our users to optionally choose to ignore the system double-tap behavior, and add a guard statement to our function to look for it:

public func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
  guard UserDefaults.standard.bool(forKey: "customPencilActionsEnabled") == false else {
    doSomethingCustom()
    return
  }

  switch UIPencilInteraction.preferredTapAction {
  case .ignore: return
  case .showColorPalette: showColorPallet()
  case .switchEraser: change(tool: .eraser)
  case .switchPrevious: changeToPreviousTool()
  }
}

That's all for now, have a question or idea for a Bite? Send it to hello@littlebitesofcocoa.com.