
Pencilkit
- 2.1k installs
- 944 repo stars
- Updated July 15, 2026
- dpearson2699/swift-ios-skills
pencilkit is an agent skill that Add Apple Pencil drawing with PKCanvasView, PKToolPicker, PKDrawing serialization/export, stroke inspection, and PencilKit/PaperKit handoffs. Use when building drawing ap.
About
The pencilkit skill. Add Apple Pencil drawing with PKCanvasView, PKToolPicker, PKDrawing serialization/export, stroke inspection, and PencilKit/PaperKit handoffs. Use when building drawing apps, annotation features, handwriting capture, signature fields, content-version-safe ink workflows, or Apple Pencil-powered experiences on iOS/iPadOS/visionOS. **Platform availability:** iOS 13+, iPadOS 13+, Mac Catalyst 13.1+, visionOS 1.0+. Use for signature pads, whiteboards, or explicit finger-drawing modes. Use when finger input should never create strokes. The canvas automatically adopts the selected tool. and custom tool picker item classes require iOS/iPadOS 18+, Mac Catalyst 18+, and visionOS 2+; those item classes are available on macOS starting in macOS 26. | Version | Content | |---|---| | | iPadOS 14-era inks: marker, pen, pencil | | | iPadOS 17 inks: monoline, fountain pen, watercolor, crayon | | | Barrel-roll angle data | | | Reed pen | In compatibility reviews, state the complete version map before recommending a cap.
- [PKCanvasView Basics](#pkcanvasview-basics)
- [PKToolPicker](#pktoolpicker)
- [PKDrawing Serialization](#pkdrawing-serialization)
- [Content Version Compatibility](#content-version-compatibility)
- [Exporting to Image](#exporting-to-image)
Pencilkit by the numbers
- 2,078 all-time installs (skills.sh)
- +105 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #104 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
pencilkit capabilities & compatibility
- Capabilities
- [pkcanvasview basics](#pkcanvasview basics) · [pktoolpicker](#pktoolpicker) · [pkdrawing serialization](#pkdrawing serializati · [content version compatibility](#content version · [exporting to image](#exporting to image)
- Use cases
- frontend · ui design · api development
What pencilkit says it does
**Platform availability:** iOS 13+, iPadOS 13+, Mac Catalyst 13.1+, visionOS 1.0+.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill pencilkitAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.1k |
|---|---|
| repo stars | ★ 944 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 15, 2026 |
| Repository | dpearson2699/swift-ios-skills ↗ |
How do I apply pencilkit correctly using the SKILL.md workflows and reference files?
Add Apple Pencil drawing with PKCanvasView, PKToolPicker, PKDrawing serialization/export, stroke inspection, and PencilKit/PaperKit handoffs. Use when building drawing apps, annotation features, handw
Who is it for?
Developers and software engineers working with pencilkit patterns from the skill documentation.
Skip if: Skip when cached docs are empty, boilerplate-only, or outside the skill documented scope.
When should I use this skill?
Add Apple Pencil drawing with PKCanvasView, PKToolPicker, PKDrawing serialization/export, stroke inspection, and PencilKit/PaperKit handoffs. Use when building drawing apps, annotation features, handwriting capture, sign
What you get
Grounded pencilkit guidance with highlights, triggers, and evidence quotes from SKILL.md.
- PencilKit implementation plan
- Canvas wrapper architecture
- Save/load serialization approach
Files
PencilKit
Capture Apple Pencil and finger input using PKCanvasView, manage drawing tools with PKToolPicker, serialize drawings with PKDrawing, and wrap PencilKit in SwiftUI. Targets Swift 6.3 / iOS 26+.
Contents
- Setup
- PKCanvasView Basics
- PKToolPicker
- PKDrawing Serialization
- Content Version Compatibility
- Exporting to Image
- Stroke Inspection
- SwiftUI Integration
- PaperKit Relationship
- Common Mistakes
- Review Checklist
- References
Setup
PencilKit requires no entitlements or Info.plist entries. Import PencilKit and create a PKCanvasView.
import PencilKitPlatform availability: iOS 13+, iPadOS 13+, Mac Catalyst 13.1+, visionOS 1.0+.
PKCanvasView Basics
PKCanvasView is a UIScrollView subclass that captures Apple Pencil and finger input and renders strokes.
import PencilKit
import UIKit
class DrawingViewController: UIViewController, PKCanvasViewDelegate {
let canvasView = PKCanvasView()
override func viewDidLoad() {
super.viewDidLoad()
canvasView.delegate = self
canvasView.drawingPolicy = .anyInput
canvasView.tool = PKInkingTool(.pen, color: .black, width: 5)
canvasView.frame = view.bounds
canvasView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(canvasView)
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
// Drawing changed -- save or process
}
}Drawing Policies
| Policy | Behavior |
|---|---|
.default | Respects UIPencilInteraction.prefersPencilOnlyDrawing when the tool picker is visible; otherwise Pencil-only |
.anyInput | Both pencil and finger draw |
.pencilOnly | Only Apple Pencil touches draw on the canvas |
canvasView.drawingPolicy = .pencilOnlyUse .default for system-standard Pencil-primary canvases when the tool picker's drawing-policy control should follow the user's Pencil preference. Use .anyInput for signature pads, whiteboards, or explicit finger-drawing modes. Use .pencilOnly when finger input should never create strokes.
Configuring the Canvas
// Set a large drawing area (scrollable)
canvasView.contentSize = CGSize(width: 2000, height: 3000)
// Enable/disable the ruler
canvasView.isRulerActive = true
// Set the current tool programmatically
canvasView.tool = PKInkingTool(.pencil, color: .blue, width: 3)
canvasView.tool = PKEraserTool(.vector)PKToolPicker
PKToolPicker displays a floating palette of drawing tools. The canvas automatically adopts the selected tool.
class DrawingViewController: UIViewController {
let canvasView = PKCanvasView()
let toolPicker = PKToolPicker()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
toolPicker.addObserver(canvasView)
toolPicker.setVisible(true, forFirstResponder: canvasView)
canvasView.becomeFirstResponder()
}
}Custom Tool Picker Items
Create a tool picker with specific tools. PKToolPicker(toolItems:) and custom tool picker item classes require iOS/iPadOS 18+, Mac Catalyst 18+, and visionOS 2+; those item classes are available on macOS starting in macOS 26.
let toolPicker = PKToolPicker(toolItems: [
PKToolPickerInkingItem(type: .pen, color: .black, width: 5),
PKToolPickerInkingItem(type: .pencil, color: .gray, width: 5),
PKToolPickerInkingItem(type: .marker, color: .yellow, width: 12),
PKToolPickerEraserItem(type: .vector),
PKToolPickerLassoItem(),
PKToolPickerRulerItem()
])Ink Types
| Type | Description |
|---|---|
.pen | Smooth, pressure-sensitive pen |
.pencil | Textured pencil with tilt shading |
.marker | Semi-transparent highlighter |
.monoline | Uniform-width pen |
.fountainPen | Variable-width calligraphy pen |
.watercolor | Blendable watercolor brush |
.crayon | Textured crayon |
.reed | Reed pen (iOS/iPadOS/macOS/visionOS 26+) |
Content Versions
When drawings sync to older OS versions, check requiredContentVersion before uploading or cap new content by setting maximumSupportedContentVersion on both the PKCanvasView and PKToolPicker.
| Version | Content |
|---|---|
.version1 | iPadOS 14-era inks: marker, pen, pencil |
.version2 | iPadOS 17 inks: monoline, fountain pen, watercolor, crayon |
.version3 | Barrel-roll angle data |
.version4 | Reed pen |
In compatibility reviews, state the complete version map before recommending a cap. If the plan exposes a curated picker or specific ink choices, also mention the availability of PKToolPicker(toolItems:) and custom picker item APIs. When existing content exceeds the target OS version, sync a verified fallback PKDrawing or restrict editing up front; do not rely only on a warning.
PKDrawing Serialization
PKDrawing is a value type (struct) that holds all stroke data. Serialize it to Data for persistence.
// Save
func saveDrawing(_ drawing: PKDrawing) throws {
let data = drawing.dataRepresentation()
try data.write(to: fileURL)
}
// Load
func loadDrawing() throws -> PKDrawing {
let data = try Data(contentsOf: fileURL)
return try PKDrawing(data: data)
}When loading synced or user-provided drawings, handle decode failures explicitly instead of suppressing them with try?:
do {
canvasView.drawing = try PKDrawing(data: data)
} catch {
showReadOnlyPreview(for: document, loadError: error)
}Combining Drawings
var drawing1 = PKDrawing()
let drawing2 = PKDrawing()
drawing1.append(drawing2)
// Non-mutating
let combined = drawing1.appending(drawing2)Transforming Drawings
let scaled = drawing.transformed(using: CGAffineTransform(scaleX: 2, y: 2))
let translated = drawing.transformed(using: CGAffineTransform(translationX: 100, y: 0))Content Version Compatibility
For sync, migration, downgrade, or cross-device editing tasks, use requiredContentVersion as the compatibility gate and choose an explicit maximumSupportedContentVersion when old clients must keep editing.
let targetVersion: PKContentVersion = .version1
canvasView.maximumSupportedContentVersion = targetVersion
toolPicker.maximumSupportedContentVersion = targetVersion
switch drawing.requiredContentVersion {
case .version1:
// Older marker, pen, and pencil ink set
syncEditable(drawing)
case .version2:
// iPadOS 17-era inks: monoline, fountain pen, watercolor, crayon
syncIfRecipientsSupportVersion2(drawing)
case .version3, .version4:
// Later features such as barrel-roll data and Reed Pen
syncEditableOnlyToCurrentClients(drawing)
@unknown default:
showReadOnlyPreview(for: drawing)
}If a drawing requires a newer version than a recipient can load, preserve the full-fidelity PKDrawing for capable clients and provide a read-only preview or separate fallback instead of silently overwriting it. See references/pencilkit-patterns.md for the deeper compatibility table.
Exporting to Image
Generate a UIImage from a drawing.
func exportImage(from drawing: PKDrawing, scale: CGFloat = 2.0) -> UIImage {
drawing.image(from: drawing.bounds, scale: scale)
}
// Export a specific region
let region = CGRect(x: 0, y: 0, width: 500, height: 500)
let scale = UITraitCollection.current.displayScale
let croppedImage = drawing.image(from: region, scale: scale)Stroke Inspection
Access individual strokes, their ink, and control points.
for stroke in drawing.strokes {
let ink = stroke.ink
print("Ink type: \(ink.inkType), color: \(ink.color)")
print("Bounds: \(stroke.renderBounds)")
// Access path points
let path = stroke.path
print("Points: \(path.count), created: \(path.creationDate)")
// Interpolate along the path
for point in path.interpolatedPoints(by: .distance(10)) {
print("Location: \(point.location), force: \(point.force)")
}
}Constructing Strokes Programmatically
let points = [
PKStrokePoint(location: CGPoint(x: 0, y: 0), timeOffset: 0,
size: CGSize(width: 5, height: 5), opacity: 1,
force: 0.5, azimuth: 0, altitude: .pi / 2),
PKStrokePoint(location: CGPoint(x: 100, y: 100), timeOffset: 0.1,
size: CGSize(width: 5, height: 5), opacity: 1,
force: 0.5, azimuth: 0, altitude: .pi / 2)
]
let path = PKStrokePath(controlPoints: points, creationDate: Date())
let stroke = PKStroke(ink: PKInk(.pen, color: .black), path: path,
transform: .identity, mask: nil)
let drawing = PKDrawing(strokes: [stroke])SwiftUI Integration
Wrap PKCanvasView in a UIViewRepresentable for SwiftUI.
import SwiftUI
import PencilKit
struct CanvasView: UIViewRepresentable {
@Binding var drawing: PKDrawing
@Binding var toolPickerVisible: Bool
func makeUIView(context: Context) -> PKCanvasView {
let canvas = PKCanvasView()
canvas.delegate = context.coordinator
canvas.drawingPolicy = .anyInput
canvas.drawing = drawing
context.coordinator.toolPicker.addObserver(canvas)
return canvas
}
func updateUIView(_ canvas: PKCanvasView, context: Context) {
if canvas.drawing != drawing {
canvas.drawing = drawing
}
let toolPicker = context.coordinator.toolPicker
toolPicker.setVisible(toolPickerVisible, forFirstResponder: canvas)
if toolPickerVisible { canvas.becomeFirstResponder() }
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, PKCanvasViewDelegate {
let parent: CanvasView
let toolPicker = PKToolPicker()
init(_ parent: CanvasView) {
self.parent = parent
super.init()
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
parent.drawing = canvasView.drawing
}
}
}For SwiftUI wrappers, call out the input-policy choice in the wrapper guidance. Use .anyInput when finger drawing is part of the product. Use .pencilOnly when touch should stay reserved for scrolling or selection. Use .default when you want PencilKit's system behavior: with the tool picker visible, it follows the user's Pencil-only drawing setting; otherwise only Apple Pencil draws.
Usage in SwiftUI
struct DrawingScreen: View {
@State private var drawing = PKDrawing()
@State private var showToolPicker = true
var body: some View {
CanvasView(drawing: $drawing, toolPickerVisible: $showToolPicker)
.ignoresSafeArea()
}
}PaperKit Relationship
PaperKit (iOS 26+) extends PencilKit with a complete markup experience including shapes, text boxes, images, stickers, and loupes. Use the sibling paperkit skill when you need structured markup rather than only freeform drawing.
| Capability | PencilKit | PaperKit |
|---|---|---|
| Freeform drawing | Yes | Yes |
| Shapes & lines | No | Yes |
| Text boxes | No | Yes |
| Images & stickers | No | Yes |
| Loupes | No | Yes |
| Markup toolbar | No | Yes |
| Markup insertion UI | No | MarkupEditViewController, MarkupToolbarViewController |
| Data model | PKDrawing | PaperMarkup |
PaperKit uses PencilKit under the hood: PaperMarkupViewController accepts PKTool for its drawingTool property, and PaperMarkup can append a PKDrawing.
Common Mistakes
DON'T: Forget to call becomeFirstResponder for the tool picker
The tool picker only appears when its associated responder is first responder.
// WRONG: Tool picker never shows
toolPicker.setVisible(true, forFirstResponder: canvasView)
// CORRECT: Also become first responder
toolPicker.setVisible(true, forFirstResponder: canvasView)
canvasView.becomeFirstResponder()DON'T: Oversimplify .default drawing policy
When explaining input behavior, .default is system-setting aware. If the tool picker is visible, it respects the user's Pencil-only drawing preference; otherwise only Apple Pencil draws.
DON'T: Create multiple tool pickers for the same canvas
One PKToolPicker per canvas. Creating extras causes visual conflicts.
// WRONG
func viewDidAppear(_ animated: Bool) {
let picker = PKToolPicker() // New picker every appearance
picker.setVisible(true, forFirstResponder: canvasView)
}
// CORRECT: Store picker as a property
let toolPicker = PKToolPicker()DON'T: Ignore content versions for backward compatibility
Earlier OS versions throw when loading PKDrawing data that uses unsupported inks. Check requiredContentVersion before syncing, or set maximumSupportedContentVersion on both the canvas and tool picker to restrict new content.
// WRONG: only limits the canvas; picker can still expose newer inks
canvasView.tool = PKInkingTool(.watercolor, color: .blue)
canvasView.maximumSupportedContentVersion = .version1
// CORRECT: limit both surfaces for iPadOS 14-era ink compatibility
if #available(iOS 17.0, *) {
canvasView.maximumSupportedContentVersion = .version1
toolPicker.maximumSupportedContentVersion = .version1
}DON'T: Compare drawings by data representation
dataRepresentation() is for persistence and interchange, not comparison. Use PKDrawing equality for exact value checks, and inspect strokes or rendered images for visual/approximate comparisons.
// WRONG
if drawing1.dataRepresentation() == drawing2.dataRepresentation() { }
// CORRECT
if drawing1 == drawing2 { }Review Checklist
- [ ]
PKCanvasView.drawingPolicyset appropriately and.defaultexplained as system-setting aware - [ ]
PKToolPickerstored as a property, not recreated each appearance - [ ]
canvasView.becomeFirstResponder()called to show the tool picker - [ ] Canvas added as a
PKToolPickerobserver before showing the picker - [ ] Drawing serialized via
dataRepresentation()and loaded viaPKDrawing(data:) - [ ]
canvasViewDrawingDidChangedelegate method used to track changes - [ ]
maximumSupportedContentVersionset on both canvas and tool picker if backward compatibility is needed - [ ] Custom tool picker item code guarded for iOS/iPadOS 18+ and visionOS 2+
- [ ] Exported images use appropriate scale factor for the device
- [ ] SwiftUI wrapper avoids infinite update loops by checking
drawing != binding - [ ] Drawing bounds checked before image export (empty drawings have
.zerobounds)
References
- Extended PencilKit patterns (advanced strokes, content versions, delegates): references/pencilkit-patterns.md
- PencilKit framework
- PKCanvasView
- PKDrawing
- PKToolPicker
- PKInkingTool
- PKStroke
- Drawing with PencilKit
- Configuring the PencilKit tool picker
{
"skill_name": "pencilkit",
"evals": [
{
"id": 0,
"prompt": "I'm building a SwiftUI drawing screen for iPad with Apple Pencil and optional finger drawing. Sketch the PencilKit wrapper, tool picker setup, save/load path, and the lifecycle details that prevent stale tools or update loops.",
"expected_output": "A SwiftUI PencilKit implementation plan that wraps PKCanvasView, retains and observes PKToolPicker, chooses an appropriate drawingPolicy, serializes PKDrawing with dataRepresentation()/PKDrawing(data:), and avoids binding update loops.",
"files": [],
"expectations": [
"Wraps PKCanvasView with UIViewRepresentable and uses a Coordinator as PKCanvasViewDelegate.",
"Retains PKToolPicker outside a local scope and adds the canvas as an observer before showing the picker or becoming first responder.",
"Explains the difference between .default, .anyInput, and .pencilOnly enough to choose one for Pencil-primary versus finger-drawing modes.",
"Persists drawings using PKDrawing.dataRepresentation() and reloads with PKDrawing(data:).",
"Avoids SwiftUI update loops by checking whether the canvas drawing already matches the binding before assigning."
]
},
{
"id": 1,
"prompt": "Review this PencilKit compatibility plan: let users pick watercolor, crayon, and reed, save the PKDrawing bytes to CloudKit, and set canvasView.maximumSupportedContentVersion = .version2 so iPadOS 16 clients can open every drawing. What should change?",
"expected_output": "A correction-focused review that explains PencilKit content versions, unsupported-ink load failures, the need to set maximumSupportedContentVersion on both canvas and tool picker, and the version boundary for iPadOS 17 and iOS 26 inks.",
"files": [],
"expectations": [
"States that unsupported newer inks cause older systems to fail when loading PKDrawing data rather than guaranteeing compatibility.",
"Correctly maps version1 to marker/pen/pencil-era compatibility, version2 to iPadOS 17 inks, version3 to barrel-roll angle data, and version4 to reed.",
"Explains that .version2 does not make drawings loadable by iPadOS 16 clients; use .version1 or a fallback for pre-iPadOS 17 compatibility.",
"Sets maximumSupportedContentVersion on both PKCanvasView and PKToolPicker when restricting new content.",
"Calls out .reed as iOS/iPadOS/macOS/visionOS 26+ and custom tool picker items as iOS/iPadOS 18+ or visionOS 2+."
]
},
{
"id": 2,
"prompt": "I need an annotation experience with freehand Apple Pencil signatures, movable text boxes, arrows, image stickers, and a standard markup toolbar. Which parts should stay in PencilKit and when should I switch to PaperKit?",
"expected_output": "A boundary-aware answer that keeps freeform drawing, tool picker, stroke inspection, image export, and PKDrawing serialization in PencilKit while routing structured markup and toolbar needs to PaperKit.",
"files": [],
"expectations": [
"Keeps PencilKit scope to PKCanvasView, PKToolPicker, PKDrawing serialization/export, stroke inspection, and signature/freehand drawing.",
"Routes shapes, arrows, text boxes, images/stickers, loupes, and system-standard markup toolbar work to PaperKit.",
"Mentions the verified bridge points: PaperMarkupViewController.drawingTool accepts a PKTool and PaperMarkup can append PKDrawing.",
"Does not claim PencilKit directly owns structured markup elements such as movable text boxes or stickers.",
"Preserves a practical handoff plan between saved PKDrawing content and a PaperKit markup workflow."
]
}
]
}
PencilKit Extended Patterns
Overflow reference for the pencilkit skill. Contains advanced patterns that exceed the main skill file's scope.
Contents
- Tool Picker Observer Pattern
- Custom Tool Picker Items
- Canvas View Delegate Lifecycle
- Undo/Redo Support
- Thumbnail Generation
- Drawing Comparison and Scoring
- Content Version Management
- Advanced SwiftUI Wrapper
Tool Picker Observer Pattern
Observe tool picker changes to update custom UI or track tool usage.
import PencilKit
class DrawingController: UIViewController, PKToolPickerObserver {
let canvasView = PKCanvasView()
let toolPicker = PKToolPicker()
override func viewDidLoad() {
super.viewDidLoad()
toolPicker.addObserver(self)
toolPicker.addObserver(canvasView)
}
func toolPickerSelectedToolItemDidChange(_ toolPicker: PKToolPicker) {
let item = toolPicker.selectedToolItem
print("Selected tool: \(item.identifier)")
}
func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) {
print("Picker visible: \(toolPicker.isVisible)")
}
func toolPickerFramesObscuredDidChange(_ toolPicker: PKToolPicker) {
let obscured = toolPicker.frameObscured(in: view)
// Adjust content insets to avoid overlap
canvasView.contentInset.bottom = obscured.height
}
}Custom Tool Picker Items
Create custom tools with unique behaviors and icons. Custom tool picker items require iOS/iPadOS 18+, Mac Catalyst 18+, or visionOS 2+.
var customConfig = PKToolPickerCustomItem.Configuration(
identifier: "com.app.highlighter",
name: "Highlighter"
)
customConfig.defaultColor = .yellow
customConfig.allowsColorSelection = true
customConfig.defaultWidth = 20
customConfig.widthVariants = [
10: UIImage(systemName: "line.diagonal")!,
20: UIImage(systemName: "line.3.horizontal")!,
40: UIImage(systemName: "rectangle.fill")!
]
customConfig.imageProvider = { item in
// Return a custom image based on current color/width
let config = UIImage.SymbolConfiguration(pointSize: 24)
return UIImage(systemName: "highlighter", withConfiguration: config)!
}
let customItem = PKToolPickerCustomItem(configuration: customConfig)
let toolPicker = PKToolPicker(toolItems: [
PKToolPickerInkingItem(type: .pen, color: .black, width: 5),
customItem,
PKToolPickerEraserItem(type: .vector)
])Canvas View Delegate Lifecycle
Track the complete drawing lifecycle.
class DrawingManager: NSObject, PKCanvasViewDelegate {
var hasUnsavedChanges = false
var isCurrentlyDrawing = false
func canvasViewDidBeginUsingTool(_ canvasView: PKCanvasView) {
isCurrentlyDrawing = true
}
func canvasViewDidEndUsingTool(_ canvasView: PKCanvasView) {
isCurrentlyDrawing = false
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
hasUnsavedChanges = true
}
func canvasViewDidFinishRendering(_ canvasView: PKCanvasView) {
// Safe to capture a snapshot for thumbnails
}
}Undo/Redo Support
PKCanvasView automatically integrates with UndoManager.
class DrawingViewController: UIViewController {
let canvasView = PKCanvasView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(canvasView)
navigationItem.leftBarButtonItem = UIBarButtonItem(
systemItem: .undo,
primaryAction: UIAction { [weak self] _ in
self?.canvasView.undoManager?.undo()
}
)
navigationItem.rightBarButtonItem = UIBarButtonItem(
systemItem: .redo,
primaryAction: UIAction { [weak self] _ in
self?.canvasView.undoManager?.redo()
}
)
}
}Thumbnail Generation
Generate thumbnails for document browsers or galleries.
func generateThumbnail(
for drawing: PKDrawing,
size: CGSize,
scale: CGFloat = 2.0
) -> UIImage? {
let bounds = drawing.bounds
guard !bounds.isEmpty else { return nil }
let aspectRatio = bounds.width / bounds.height
let targetAspect = size.width / size.height
var renderRect = bounds
if aspectRatio > targetAspect {
let scaleFactor = size.width / bounds.width
renderRect = CGRect(
x: bounds.minX,
y: bounds.midY - (size.height / scaleFactor) / 2,
width: bounds.width,
height: size.height / scaleFactor
)
} else {
let scaleFactor = size.height / bounds.height
renderRect = CGRect(
x: bounds.midX - (size.width / scaleFactor) / 2,
y: bounds.minY,
width: size.width / scaleFactor,
height: bounds.height
)
}
return drawing.image(from: renderRect, scale: scale)
}Drawing Comparison and Scoring
Compare two drawings by analyzing their strokes and points.
func strokeSimilarity(
reference: PKDrawing,
candidate: PKDrawing,
tolerance: CGFloat = 20
) -> Double {
let refPoints = reference.strokes.flatMap { stroke in
stroke.path.interpolatedPoints(by: .distance(5)).map(\.location)
}
let candPoints = candidate.strokes.flatMap { stroke in
stroke.path.interpolatedPoints(by: .distance(5)).map(\.location)
}
guard !refPoints.isEmpty else { return 0 }
var matchCount = 0
for refPoint in refPoints {
let minDist = candPoints.map { point in
hypot(refPoint.x - point.x, refPoint.y - point.y)
}.min() ?? .infinity
if minDist <= tolerance { matchCount += 1 }
}
return Double(matchCount) / Double(refPoints.count)
}Content Version Management
Handle backward compatibility when sharing drawings across OS versions.
// Check if a drawing uses features beyond a version
let drawing = canvasView.drawing
let version = drawing.requiredContentVersion
switch version {
case .version1:
// iPadOS 14-era inks: marker, pen, pencil
break
case .version2:
// iPadOS 17 inks: monoline, fountain pen, watercolor, crayon
break
case .version3:
// Barrel-roll angle data
break
case .version4:
// Reed pen
break
@unknown default:
break
}
// Limit both canvas and picker to a specific version.
// Use .version1 when saved drawings must load on pre-iPadOS 17 systems.
if #available(iOS 17.0, *) {
canvasView.maximumSupportedContentVersion = .version1
toolPicker.maximumSupportedContentVersion = .version1
}When you allow newer inks, branch before CloudKit or cross-device sync and upload either the original drawing or a verified fallback drawing.
func drawingForPreiPadOS17Sync(_ drawing: PKDrawing) -> PKDrawing? {
switch drawing.requiredContentVersion {
case .version1:
return drawing
case .version2, .version3, .version4:
let fallback = version1Fallback(from: drawing)
guard fallback.requiredContentVersion == .version1 else {
// Reusing paths can preserve newer metadata, such as barrel-roll data.
// Sync a thumbnail/message instead of incompatible drawing data.
return nil
}
return fallback
@unknown default:
return nil
}
}
func version1Fallback(from drawing: PKDrawing) -> PKDrawing {
let strokes = drawing.strokes.map { stroke -> PKStroke in
var fallback = stroke
fallback.ink = PKInkingTool(.pen, color: .black, width: 2).ink
return fallback
}
return PKDrawing(strokes: strokes)
}Advanced SwiftUI Wrapper
A full-featured SwiftUI wrapper with tool picker, undo, and save support.
import SwiftUI
import PencilKit
struct DrawingCanvas: UIViewRepresentable {
@Binding var drawing: PKDrawing
var drawingPolicy: PKCanvasViewDrawingPolicy = .anyInput
var showToolPicker: Bool = true
func makeUIView(context: Context) -> PKCanvasView {
let canvas = PKCanvasView()
canvas.delegate = context.coordinator
canvas.drawingPolicy = drawingPolicy
canvas.drawing = drawing
canvas.backgroundColor = .clear
canvas.isOpaque = false
let coordinator = context.coordinator
coordinator.toolPicker.addObserver(canvas)
return canvas
}
func updateUIView(_ canvas: PKCanvasView, context: Context) {
let coordinator = context.coordinator
if canvas.drawing != drawing {
canvas.drawing = drawing
}
coordinator.toolPicker.setVisible(showToolPicker, forFirstResponder: canvas)
if showToolPicker {
canvas.becomeFirstResponder()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, PKCanvasViewDelegate {
let parent: DrawingCanvas
let toolPicker = PKToolPicker()
init(parent: DrawingCanvas) {
self.parent = parent
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
parent.drawing = canvasView.drawing
}
}
}Related skills
How it compares
Use pencilkit for opinionated SwiftUI PencilKit architecture instead of generic SwiftUI tutorials that skip PKToolPicker lifecycle edge cases.
FAQ
Who is pencilkit for?
Developers and software engineers working with pencilkit patterns from the skill documentation.
When should I use pencilkit?
Add Apple Pencil drawing with PKCanvasView, PKToolPicker, PKDrawing serialization/export, stroke inspection, and PencilKit/PaperKit handoffs. Use when building drawing apps, annotation features, handwriting capture, signature fields, content-version-safe ink workflows, or Apple P
Is pencilkit safe to install?
Review the Security Audits panel on this page before installing in production.