PKCanvasView: Apple Pencil interrupts an active finger drawing with .anyInput

I’m testing simultaneous finger and Apple Pencil input with PencilKit and have reduced the behaviour to a minimal PKCanvasView.

import SwiftUI

struct ContentView: View {
    var body: some View {
        PencilCanvas()
            .ignoresSafeArea()
    }
}

struct PencilCanvas: UIViewRepresentable {
    func makeUIView(context: Context) -> PKCanvasView {
        let canvas = PKCanvasView()
        canvas.drawingPolicy = .anyInput
        return canvas
    }

    func updateUIView(_ canvas: PKCanvasView, context: Context) {}
}

On a physical iPad with Apple Pencil, I consistently see this behaviour:

  1. Start drawing with a finger and keep the finger moving.
  2. Touch the canvas with Apple Pencil.
  3. The active finger stroke immediately stops, while the Pencil can draw.

The reverse order behaves differently:

  1. Start drawing with Apple Pencil and keep it moving.
  2. Touch/draw with a finger.
  3. The Pencil stroke continues rather than being cancelled.

I’ve also confirmed that a newly created PKCanvasView reports isMultipleTouchEnabled == true, and explicitly setting it to true does not change the behaviour.

Is simultaneous independent Apple Pencil and finger drawing supported by PKCanvasView when using .drawingPolicy = .anyInput?

If so, is there a supported configuration or gesture-recognizer setting required to prevent the Pencil from cancelling an active finger stroke?

Or is Pencil taking priority over an active finger drawing gesture expected behaviour in PencilKit?

Answered by OneHourIndieDev in 904663022

I found a solution to the issue I was seeing where an Apple Pencil contact would interrupt an already-active finger stroke in PKCanvasView.

The approach that worked was to intercept new contacts at the parent view once PencilKit reports that the current stroke has begun. The existing touch remains associated with the canvas, while the new contact is hit-tested to the parent instead of entering the PKCanvasView hierarchy.

Minimal example:

import SwiftUI
import PencilKit
import UIKit
@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            CanvasExperiment()
                .ignoresSafeArea()
        }
    }
}
@MainActor
final class GatedCanvasHost: UIView, PKCanvasViewDelegate {
    let canvas = PKCanvasView()
    private var toolActive = false
    override init(frame: CGRect) {
        super.init(frame: frame)
        canvas.drawingPolicy = .anyInput
        canvas.delegate = self
        addSubview(canvas)
    }
    required init?(coder: NSCoder) {
        fatalError()
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        canvas.frame = bounds
    }
    override func hitTest(
        _ point: CGPoint,
        with event: UIEvent?
    ) -> UIView? {
        guard !toolActive else {
            // Existing touch remains with PKCanvasView.
            // New contacts are intercepted by the parent.
            return self
        }
        return super.hitTest(point, with: event)
    }
    func canvasViewDidBeginUsingTool(_ canvasView: PKCanvasView) {
        toolActive = true
    }
    func canvasViewDidEndUsingTool(_ canvasView: PKCanvasView) {
        toolActive = false
    }
    // Consume contacts intercepted by the parent.
    override func touchesBegan(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
    override func touchesMoved(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
    override func touchesEnded(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
    override func touchesCancelled(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
}
struct CanvasExperiment: UIViewRepresentable {
    func makeUIView(context: Context) -> GatedCanvasHost {
        GatedCanvasHost(frame: .zero)
    }
    func updateUIView(
        _ uiView: GatedCanvasHost,
        context: Context
    ) {}
}

With this setup:

  • Start drawing with a finger.
  • Keep the finger down and moving.
  • Touch the same canvas with Apple Pencil.
  • The Pencil contact is intercepted by GatedCanvasHost.
  • The original finger stroke continues rather than being terminated.

I also tested the reverse: if Pencil is already drawing, a new finger contact is intercepted and the Pencil stroke continues.

The important distinction from gesture-recognizer filtering is that the second contact is prevented from entering the PKCanvasView hierarchy in the first place.

This is intentionally a single-canvas minimal example. In my actual two-region test I applied the same gate independently to each canvas, which allowed one active contact per canvas while both canvases remained active simultaneously.

Update after further debugging:

I instrumented the minimal PKCanvasView test to inspect both PKDrawingGestureRecognizer and the raw UITouch events. This appears to confirm that PencilKit is actively ending the finger input when Apple Pencil begins.

With a finger actively drawing, introducing the Pencil causes PKDrawingGestureRecognizer to report:

BEGAN | touches: 1 CHANGED | touches: 1 ENDED | touches: 2

I also subclassed PKCanvasView to log the raw touch type and phase. In a test where I started drawing with my finger, kept the finger physically on the screen, and then introduced Apple Pencil, I got:

12256.4750 | FINGER BEGAN | phase: 0 12259.1747 | FINGER ENDED | phase: 3

The FINGER ENDED occurs when the Pencil touches the screen, despite my finger remaining physically down.

A fresh PKCanvasView reports isMultipleTouchEnabled == true and drawingGestureRecognizer.cancelsTouchesInView == false, so this does not appear to be normal gesture-recognizer touch cancellation.

The behaviour is also asymmetric: if Apple Pencil is drawing first, introducing a finger does not interrupt the Pencil stroke.

Is this expected PencilKit behaviour, and is there a supported API or configuration that prevents Apple Pencil input from terminating an already-active finger stroke?

Further update after testing a custom UIGestureRecognizer:

PKCanvasView.drawingGestureRecognizer.ignore(pencilTouch, for: event)

This successfully prevents that Pencil touch from drawing, so the Pencil touch is being ignored by the exposed drawing gesture recognizer.

However, the already-active finger stroke is still immediately ended (FINGER ENDED) when the Pencil touches the screen, despite the finger remaining physically down.

After this occurs, PencilKit can also leave its drawing recognizer active and blocking its gesture-recognizer subgraph, with subsequent finger strokes producing no ink until a later Pencil interaction.

So even when the incoming Pencil touch is explicitly ignored by drawingGestureRecognizer, its arrival still causes PencilKit to terminate the active finger stroke.

Is there another supported PencilKit mechanism for preventing this Pencil-over-finger arbitration, or is this behaviour internal to PKCanvasView?

Accepted Answer

I found a solution to the issue I was seeing where an Apple Pencil contact would interrupt an already-active finger stroke in PKCanvasView.

The approach that worked was to intercept new contacts at the parent view once PencilKit reports that the current stroke has begun. The existing touch remains associated with the canvas, while the new contact is hit-tested to the parent instead of entering the PKCanvasView hierarchy.

Minimal example:

import SwiftUI
import PencilKit
import UIKit
@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            CanvasExperiment()
                .ignoresSafeArea()
        }
    }
}
@MainActor
final class GatedCanvasHost: UIView, PKCanvasViewDelegate {
    let canvas = PKCanvasView()
    private var toolActive = false
    override init(frame: CGRect) {
        super.init(frame: frame)
        canvas.drawingPolicy = .anyInput
        canvas.delegate = self
        addSubview(canvas)
    }
    required init?(coder: NSCoder) {
        fatalError()
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        canvas.frame = bounds
    }
    override func hitTest(
        _ point: CGPoint,
        with event: UIEvent?
    ) -> UIView? {
        guard !toolActive else {
            // Existing touch remains with PKCanvasView.
            // New contacts are intercepted by the parent.
            return self
        }
        return super.hitTest(point, with: event)
    }
    func canvasViewDidBeginUsingTool(_ canvasView: PKCanvasView) {
        toolActive = true
    }
    func canvasViewDidEndUsingTool(_ canvasView: PKCanvasView) {
        toolActive = false
    }
    // Consume contacts intercepted by the parent.
    override func touchesBegan(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
    override func touchesMoved(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
    override func touchesEnded(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
    override func touchesCancelled(
        _ touches: Set<UITouch>,
        with event: UIEvent?
    ) {}
}
struct CanvasExperiment: UIViewRepresentable {
    func makeUIView(context: Context) -> GatedCanvasHost {
        GatedCanvasHost(frame: .zero)
    }
    func updateUIView(
        _ uiView: GatedCanvasHost,
        context: Context
    ) {}
}

With this setup:

  • Start drawing with a finger.
  • Keep the finger down and moving.
  • Touch the same canvas with Apple Pencil.
  • The Pencil contact is intercepted by GatedCanvasHost.
  • The original finger stroke continues rather than being terminated.

I also tested the reverse: if Pencil is already drawing, a new finger contact is intercepted and the Pencil stroke continues.

The important distinction from gesture-recognizer filtering is that the second contact is prevented from entering the PKCanvasView hierarchy in the first place.

This is intentionally a single-canvas minimal example. In my actual two-region test I applied the same gate independently to each canvas, which allowed one active contact per canvas while both canvases remained active simultaneously.

PKCanvasView: Apple Pencil interrupts an active finger drawing with .anyInput
 
 
Q