Pencilkit

作者 dpearson26998d90fd121a26無授權條款1.1K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫2 個月前更新

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.

AI 產生的概覽

指導在 iOS、iPadOS 與 visionOS 上使用 PencilKit 打造 Apple Pencil 繪圖與標註功能。

功能
說明如何用 PKCanvasView 擷取輸入、用 PKToolPicker 管理工具、用 PKDrawing 序列化繪圖,並將繪圖匯出為影像。內容也涵蓋內容版本相容性、筆畫檢查、SwiftUI 封裝,以及與 PaperKit 的關係。
適用情境
適用於在 iOS、iPadOS 或 visionOS 上打造繪圖 App、標註功能、手寫擷取、簽名欄位或其他 Apple Pencil 體驗。
執行需求
需要 Swift 與 PencilKit 框架;不需要權限或 Info.plist 項目。不附帶指令碼,只有說明與一個參考檔案。

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

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+.

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 decode validate/fix/retry loop. Do not mark bytes valid when PKDrawing(data:) still throws.
  3. 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.
  4. 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.
  5. 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.

swift
import PencilKitimport 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
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 rulercanvasView.isRulerActive = true
// Set the current tool programmaticallycanvasView.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

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.

swift
// Savefunc saveDrawing(_ drawing: PKDrawing) throws {    let data = drawing.dataRepresentation()    try data.write(to: fileURL, options: .atomic)}
// Loadfunc 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?.

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

Combining Drawings

swift
var drawing1 = PKDrawing()let drawing2 = PKDrawing()drawing1.append(drawing2)
// Non-mutatinglet 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 = .version1canvasView.maximumSupportedContentVersion = targetVersiontoolPicker.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 [blocked] 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 regionlet region = CGRect(x: 0, y: 0, width: 500, height: 500)let scale = UITraitCollection.current.displayScalelet 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("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 [blocked] only for generated ink paths; ordinary drawing and inspection do not need the advanced constructors.

SwiftUI Integration

Wrap PKCanvasView in a UIViewRepresentable for SwiftUI.

swift
import SwiftUIimport 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

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

swift
// WRONG: Tool picker never showstoolPicker.setVisible(true, forFirstResponder: canvasView)
// CORRECT: Also become first respondertoolPicker.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.

swift
// WRONGfunc viewDidAppear(_ animated: Bool) {    let picker = PKToolPicker()  // New picker every appearance    picker.setVisible(true, forFirstResponder: canvasView)}
// CORRECT: Store picker as a propertylet 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.

swift
// WRONGif drawing1.dataRepresentation() == drawing2.dataRepresentation() { }
// CORRECTif 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

來源與署名

來源:dpearson2699/swift-ios-skills位於skills/pencilkit提交8d90fd1

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架

更多來自 dpearson2699/swift-ios-skills 的技能

Widgetkit

dpearson2699

指導實作、審查與改進 iOS、iPadOS、watchOS 與 CarPlay 上的 WidgetKit 小工具與控制項。

Software Development1.1K2 個月前更新

Weatherkit

dpearson2699

指導 iOS 開發者使用 WeatherService 取得 WeatherKit 預報、警報與署名資訊。

Software Development1.1K2 個月前更新

Vision Framework

dpearson2699

Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision.

待分類1.1K2 個月前更新

Tipkit

dpearson2699

Implement and review Apple TipKit feature-discovery UI for iOS 17+ apps. Use when adding or auditing in-app tips, contextual help, coach marks, Tip, TipView, popoverTip, rules, events, actions, display frequency, testing overrides, reusable tip identifiers, or iOS 18+ TipGroup and CloudKit tip sync; avoid for generic SwiftUI navigation or layout outside tip presentation.

待分類1.1K2 個月前更新

Tabletopkit

dpearson2699

指導使用 TabletopKit 在 visionOS 上打造多人空間桌遊,涵蓋棋具、座位、動作與 RealityKit 算繪。

Software Development1.1K2 個月前更新

Swiftui Webkit

dpearson2699

指導在 iOS 26 及更新版本的 SwiftUI App 中使用 WebKit for SwiftUI 嵌入與控制網頁內容。

Software Development1.1K2 個月前更新