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:
- Start drawing with a finger and keep the finger moving.
- Touch the canvas with Apple Pencil.
- The active finger stroke immediately stops, while the Pencil can draw.
The reverse order behaves differently:
- Start drawing with Apple Pencil and keep it moving.
- Touch/draw with a finger.
- 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?
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.