Wardn Hub
MCP ServersSkillsCategoriesAPI docsSubmit server
Submit server
Wardn HubTrusted MCP server directory.

Registry

  • MCP Servers
  • Skills
  • Categories

Resources

  • API docs
  • Score method

Contribute

  • Submit server
  • Advertise
© 2026 Wardn Hub
Wardn Hub
MCP ServersSkillsCategoriesAPI docsSubmit server
Submit server
skills/dpearson2699/swift-ios-skills/skills-pencilkit

pencilkit

1
dpearson2699/swift-ios-skills·Art & Culture·Audit passed·Snapshot 9cd74b2b85fd
Installs
0

Summary

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.

SKILL.md

PencilKit

Capture Apple Pencil and finger input using PKCanvasView, manage drawing tools with PKToolPicker, serialize drawings with PKDrawing, and wrap PencilKit in SwiftUI.

Contents

  • Setup
  • Capture-to-Export Workflow
  • 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 PencilKit

Platform availability: iOS 13+, iPadOS 13+, Mac Catalyst 13.1+, visionOS 1.0+.

Capture-to-Export Workflow

  1. Capture: Read canvasView.drawing from canvasViewDrawingDidChange(_:); keep the previous persisted revision until the new revision completes the remaining checkpoints.
  2. Serialize: Create dataRepresentation(), write atomically, and run the . Do not mark bytes valid when still throws.
decode validate/fix/retry loop
PKDrawing(data:)
  • Version-gate: Apply Content Version Compatibility before editable sync. If the recipient cannot load the drawing, preserve the full-fidelity source and use an existing compatible fallback or read-only preview.
  • Sync: Send only validated, compatible data and mark the revision synced after acknowledgement. On transport or conflict failure, retain the pending revision, resolve the cause, and retry without discarding the last good copy.
  • Export: Validate a nonempty drawing region and intended scale before calling image(from:scale:); skip export on invalid bounds without altering the serialized drawing.
  • 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

    PolicyBehavior
    .defaultRespects UIPencilInteraction.prefersPencilOnlyDrawing when the tool picker is visible; otherwise Pencil-only
    .anyInputBoth pencil and finger draw
    .pencilOnlyOnly Apple Pencil touches draw on the canvas
    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

    // 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

    TypeDescription
    .penSmooth, pressure-sensitive pen
    .pencilTextured pencil with tilt shading
    .markerSemi-transparent highlighter
    .monolineUniform-width pen
    .fountainPenVariable-width calligraphy pen
    .watercolorBlendable watercolor brush
    .crayonTextured crayon
    .reedReed pen (iOS/iPadOS/macOS/visionOS 26+)

    Content Versions

    Use Content Version Compatibility as the single version map and compatibility gate for both the canvas and tool picker.

    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, options: .atomic)
    }
    
    // Load
    func loadDrawing() throws -> PKDrawing {
        let data = try Data(contentsOf: fileURL)
        return try PKDrawing(data: data)
    }
    

    Decode Validate/Fix/Retry Loop

    For synced or user-provided data: validate with PKDrawing(data:); on failure preserve the original bytes and fix the cause by refetching an intact revision or selecting a previously generated compatible copy; then retry the decode. Assign the drawing only after a successful retry. If recovery still fails, keep the source unchanged and show an error or available read-only preview instead of suppressing the failure with try?.

    do {
        canvasView.drawing = try PKDrawing(data: correctedData) // retry
    } 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

    Load Constructing Strokes Programmatically only for generated ink paths; ordinary drawing and inspection do not need the advanced constructors.

    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, set the input policy using the canonical Drawing Policies table.

    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.

    CapabilityPencilKitPaperKit
    Freeform drawingYesYes
    Shapes & linesNoYes
    Text boxesNoYes
    Images & stickersNoYes
    LoupesNoYes
    Markup toolbarNoYes
    Markup insertion UINoMarkupEditViewController, MarkupToolbarViewController
    Data modelPKDrawingPaperMarkup

    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: 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

    Apply the Content Version Compatibility gate to both the canvas and tool picker before syncing editable drawings.

    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.drawingPolicy follows the canonical policy table
    • PKToolPicker stored as a property, not recreated each appearance
    • canvasView.becomeFirstResponder() called to show the tool picker
    • Canvas added as a PKToolPicker observer before showing the picker
    • Drawing serialized via dataRepresentation() and loaded via PKDrawing(data:)
    • canvasViewDrawingDidChange delegate method used to track changes
    • maximumSupportedContentVersion set 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 .zero bounds)

    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

    Related skills

    accessorysetupkitactivitykitadattributionkitalarmkitapp-clips