Claude
Skills
Sign in
Back

pencilkit

Included with Lifetime
$97 forever

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.

Writing & Docs

What this skill does


# 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](#setup)
- [PKCanvasView Basics](#pkcanvasview-basics)
- [PKToolPicker](#pktoolpicker)
- [PKDrawing Serialization](#pkdrawing-serialization)
- [Content Version Compatibility](#content-version-compatibility)
- [Exporting to Image](#exporting-to-image)
- [Stroke Inspection](#stroke-inspection)
- [SwiftUI Integration](#swiftui-integration)
- [PaperKit Relationship](#paperkit-relationship)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)

## Setup

PencilKit requires no entitlements or Info.plist entries. Import `PencilKit`
and create a `PKCanvasView`.

```swift
import PencilKit
```

**Platform 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.

```swift
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 |

```swift
canvasView.drawingPolicy = .pencilOnly
```

Use `.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

```swift
// 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.

```swift
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.

```swift
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.

```swift
// 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?`:

```swift
do {
    canvasView.drawing = try PKDrawing(data: data)
} catch {
    showReadOnlyPreview(for: document, loadError: error)
}
```

### Combining Drawings

```swift
var drawing1 = PKDrawing()
let drawing2 = PKDrawing()
drawing1.append(drawing2)

// Non-mutating
let combined = drawing1.appending(drawing2)
```

### Transforming Drawings

```swift
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.

```swift
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](references/pencilkit-patterns.md) for the
deeper compatibility table.

## Exporting to Image

Generate a `UIImage` from a drawing.

```swift
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.

```swift
for stroke in drawing.strokes {
    let ink = stroke.ink
    print("Ink type: \(ink.inkType), color: \(ink.color)")
    print

Related in Writing & Docs