pencilkit

pencilkit

熱門

加入 Apple Pencil 手繪與繪圖功能,支援 PKCanvasView、PKToolPicker、PKDrawing 序列化與匯出、筆觸檢視,以及 PencilKit 與 PaperKit 之間的接續處理(handoff)。適用於在 iOS/iPadOS/visionOS 上開發繪圖應用程式、標註功能、手寫擷取、簽名欄位、相容內容版本的墨水工作流程,或打造基於 Apple Pencil 的互動體驗。

967星標
49分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
pencilkit
描述

加入 Apple Pencil 手繪與繪圖功能,支援 PKCanvasView、PKToolPicker、PKDrawing 序列化與匯出、筆觸檢視,以及 PencilKit 與 PaperKit 之間的接續處理(handoff)。適用於在 iOS/iPadOS/visionOS 上開發繪圖應用程式、標註功能、手寫擷取、簽名欄位、相容內容版本的墨水工作流程,或打造基於 Apple Pencil 的互動體驗。

PencilKit

使用 PKCanvasView 擷取 Apple Pencil 與手指輸入,透過 PKToolPicker 管理繪圖工具,使用 PKDrawing 序列化繪圖內容,並將 PencilKit 封裝至 SwiftUI 中。

Contents

Setup

PencilKit 不需要任何權限設定(Entitlements)或 Info.plist 設定項目。只需匯入 PencilKit 並建立 PKCanvasView 即可。

import PencilKit

支援平台與版本: iOS 13+、iPadOS 13+、Mac Catalyst 13.1+、visionOS 1.0+。

Capture-to-Export Workflow

  1. 擷取:canvasViewDrawingDidChange(_:) 讀取 canvasView.drawing;在新的修訂版本完成剩餘的檢查點前,請保留上一次已持久化的修訂版本。
  2. 序列化: 呼叫 dataRepresentation() 建立資料,進行原子化寫入(atomically),並執行解碼驗證/修復/重試迴圈。若 PKDrawing(data:) 仍會拋出異常,切勿將該 Data 標示為有效。
  3. 版本控管: 在進行可編輯同步前,先套用內容版本相容性。若接收端無法載入該繪圖,請保留高傳真(full-fidelity)的原始資料,並使用現有的相容降級方案或唯讀預覽。
  4. 同步: 僅傳送經過驗證且相容的資料,並在收到確認後將該修訂版本標示為已同步。發生傳輸或衝突失敗時,保留待處理的修訂版本,解決原因後重試,切勿丟棄最後一份完好的副本。
  5. 匯出: 在呼叫 image(from:scale:) 之前,先確認繪圖區域非空且縮放比例符合預期;若範圍無效則跳過匯出,且不改變已序列化的繪圖內容。

PKCanvasView Basics

PKCanvasViewUIScrollView 的子類別,專門用來擷取 Apple Pencil 與手指輸入並繪製筆觸。

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 Policies

Policy Behavior
.default 當工具選取器可見時,遵循 UIPencilInteraction.prefersPencilOnlyDrawing;否則僅限 Pencil 繪製
.anyInput Apple Pencil 與手指皆可繪製
.pencilOnly 僅允許 Apple Pencil 的觸控在畫布上繪製
canvasView.drawingPolicy = .pencilOnly

當工具選取器的繪圖策略控制項應遵循使用者的 Pencil 偏好時,系統標準且以 Pencil 為主的畫布請使用 .default。如果是簽名板、白板或明確的手指繪圖模式,請使用 .anyInput。若希望手指觸控絕不產生筆觸,請使用 .pencilOnly

Configuring the Canvas

// 設定大型繪圖區域(可滾動)
canvasView.contentSize = CGSize(width: 2000, height: 3000)

// 啟用/停用直尺
canvasView.isRulerActive = true

// 以程式碼方式設定當前工具
canvasView.tool = PKInkingTool(.pencil, color: .blue, width: 3)
canvasView.tool = PKEraserTool(.vector)

PKToolPicker

PKToolPicker 會顯示浮動的繪圖工具面板。畫布會自動套用目前選取的工具。

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

建立包含特定工具的工具選取器。使用 PKToolPicker(toolItems:) 以及自訂工具選取器項目類別需要 iOS/iPadOS 18+、Mac Catalyst 18+ 與 visionOS 2+;這些項目類別在 macOS 上則自 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 平滑且具感壓效果的原子筆
.pencil 帶有紋理與傾斜斜刷效果的鉛筆
.marker 半透明螢光筆
.monoline 等寬線條筆
.fountainPen 變寬書法鋼筆
.watercolor 可混色的水彩筆
.crayon 具質感質地的蠟筆
.reed 蘆葦筆(iOS/iPadOS/macOS/visionOS 26+)

Content Versions

使用內容版本相容性作為畫布與工具選取器的單一版本對照與相容性閘口。

PKDrawing Serialization

PKDrawing 是一個包含所有筆觸資料的值類型(struct)。可將其序列化為 Data 以進行持久化儲存。

// 儲存
func saveDrawing(_ drawing: PKDrawing) throws {
    let data = drawing.dataRepresentation()
    try data.write(to: fileURL, options: .atomic)
}

// 載入
func loadDrawing() throws -> PKDrawing {
    let data = try Data(contentsOf: fileURL)
    return try PKDrawing(data: data)
}

Decode Validate/Fix/Retry Loop

對於同步或使用者提供的資料:先用 PKDrawing(data:) 進行驗證;若解碼失敗,保留原始位元組資料,並透過重新擷取完整修訂版本或選擇先前產生的相容副本來修復原因;接著重試解碼。只有在重試成功後才能將繪圖指派給畫布。若修復後仍無法復原,請保持原始資料不變,並顯示錯誤訊息或可用的唯讀預覽,切勿使用 try? 壓制錯誤。

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

在進行同步、版本遷移、降級或跨裝置編輯任務時,請將 requiredContentVersion 作為相容性檢查閘口;若舊版用戶端必須維持編輯能力,請指定明確的 maximumSupportedContentVersion

let targetVersion: PKContentVersion = .version1
canvasView.maximumSupportedContentVersion = targetVersion
toolPicker.maximumSupportedContentVersion = targetVersion

switch drawing.requiredContentVersion {
case .version1:
    // 較舊的螢光筆、原子筆與鉛筆墨水組合
    syncEditable(drawing)
case .version2:
    // iPadOS 17 世代墨水:等寬筆、鋼筆、水彩筆、蠟筆
    syncIfRecipientsSupportVersion2(drawing)
case .version3, .version4:
    // 較新功能,例如筆身旋轉(barrel-roll)資料與蘆葦筆
    syncEditableOnlyToCurrentClients(drawing)
@unknown default:
    showReadOnlyPreview(for: drawing)
}

若繪圖所需的版本高於接收端所能載入的版本,請為具備相應能力的用戶端保留完整高傳真度(full-fidelity)的 PKDrawing,並提供唯讀預覽或獨立的降級備用方案,而不是靜默地覆蓋原始資料。更深入的相容性對照表請參閱 references/pencilkit-patterns.md

Exporting to Image

從繪圖產生 UIImage

func exportImage(from drawing: PKDrawing, scale: CGFloat = 2.0) -> UIImage {
    drawing.image(from: drawing.bounds, scale: scale)
}

// 匯出指定區域
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

存取單一筆觸、其墨水種類與控制點。

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

    // 存取路徑點
    let path = stroke.path
    print("Points: \(path.count), created: \(path.creationDate)")

    // 沿著路徑插值採樣
    for point in path.interpolatedPoints(by: .distance(10)) {
        print("Location: \(point.location), force: \(point.force)")
    }
}

Constructing Strokes Programmatically

僅在需要透過程式產生墨水路徑時載入以程式碼建立筆觸;一般繪圖與筆觸檢視不需要使用這些進階建構子。

SwiftUI Integration

PKCanvasView 封裝在 UIViewRepresentable 中以供 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
        }
    }
}

在 SwiftUI 的封裝元件中,請參照標準的繪圖策略表來設定輸入策略。

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+)擴充了 PencilKit,提供包含形狀、文字方塊、圖片、貼圖與放大鏡在內的完整標註體驗。當你需要結構化標註而非僅是自由手繪時,請搭配使用兄弟技能 paperkit

Capability PencilKit PaperKit
自由繪圖
形狀與線條
文字方塊
圖片與貼圖

<!-- truncated for translation batch; full body continues in source -->