paperkit

paperkit

熱門

使用 PaperKit 新增手繪、形狀,並提供一致的標記體驗。適用於整合 PaperMarkupViewController 進行標記編輯、加入形狀辨識、處理 PaperMarkup 資料模型、在文件編輯器中嵌入標記工具,或建置需要系統標準標記工具列的標註功能。iOS 26 全新推出。

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

使用 PaperKit 新增手繪、形狀,並提供一致的標記體驗。適用於整合 PaperMarkupViewController 進行標記編輯、加入形狀辨識、處理 PaperMarkup 資料模型、在文件編輯器中嵌入標記工具,或建置需要系統標準標記工具列的標註功能。iOS 26 全新推出。

PaperKit

Beta 敏感提醒。 PaperKit 為 iOS/iPadOS 26、macOS 26 及 visionOS 26 的全新功能。API 介面可能會有所變更。在正式發布前,請務必參閱最新的 Apple 官方文件確認細節。

PaperKit 將 PencilKit 的繪圖功能與結構化標記元素(如形狀、文字、影像及線條)整合於由 PaperMarkupViewController 管理的畫布中。

目錄

工作流程

  1. 在建構 UI 之前,請先確定文件邊界(bounds)、支援的 FeatureSet 以及持久化版本。
  2. 建立 PaperMarkup 並嵌入 PaperMarkupViewController,在 View 的生命週期內保持 Controller、工具選擇器(Tool Picker)與插入控制器的存活狀態。
  3. 使用適合該平台的插入介面,並將 PencilKit 繪圖限制在 PaperKit 文件邊界內。
  4. 於主執行緒之外進行儲存,為不向前相容的內容保留縮圖,並使用相同的功能集測試來回(Round-trip)載入。
  5. 若發生失敗,請還原原始文件位元組,修復功能集/版本/控制器不符合的問題,並重新執行編輯、儲存、重新啟動、載入、縮圖備援及復原(Undo)檢查。

載入 references/paperkit-patterns.md 以取得完整的平台設定、工具選擇器連接、持久化、縮圖、自訂功能集、程式化建構及轉移指南。

設定

PaperKit 不需要任何特殊權限(Entitlements)或 Info.plist 條目。

import PaperKit

平台支援: iOS 26.0+、iPadOS 26.0+、Mac Catalyst 26.0+、macOS 26.0+、visionOS 26.0+。

三大核心元件:

元件 角色
PaperMarkupViewController 用於建立與顯示標記及繪圖的互動式畫布
PaperMarkup 用於序列化所有標記元素與 PencilKit 繪圖的資料模型
MarkupEditViewController / MarkupToolbarViewController 用於新增標記元素的插入 UI

PaperMarkupViewController

用於互動式標記的主要視圖控制器。提供支援自由 PencilKit 繪圖與結構化標記元素的可滾動畫布。符合 Observable 及 PKToolPickerObserver 協定。

基本 UIKit 設定

import PaperKit
import PencilKit
import UIKit

class MarkupViewController: UIViewController, PaperMarkupViewController.Delegate {
    var paperVC: PaperMarkupViewController!
    var toolPicker: PKToolPicker!

    override func viewDidLoad() {
        super.viewDidLoad()

        let pageBounds = CGRect(origin: .zero, size: CGSize(width: 612, height: 792))
        let markup = PaperMarkup(bounds: pageBounds)
        let features = FeatureSet.latest

        paperVC = PaperMarkupViewController(
            markup: markup,
            supportedFeatureSet: features
        )
        paperVC.delegate = self

        addChild(paperVC)
        paperVC.view.frame = view.bounds
        paperVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(paperVC.view)
        paperVC.didMove(toParent: self)

        toolPicker = PKToolPicker()
        toolPicker.addObserver(paperVC)
        paperVC.pencilKitResponderState.activeToolPicker = toolPicker
        paperVC.pencilKitResponderState.toolPickerVisibility = .visible
    }

    func paperMarkupViewControllerDidChangeMarkup(
        _ controller: PaperMarkupViewController
    ) {
        guard let markup = controller.markup else { return }
        Task { try await save(markup) }
    }
}

重要屬性

屬性 型別 說明
markup PaperMarkup? 目前的資料模型
selectedMarkup PaperMarkup 目前選取的內容
isEditable Bool 畫布是否接受輸入
isRulerActive Bool 是否顯示尺規覆蓋層
drawingTool any PKTool 目前啟用的 PencilKit 繪圖工具
contentView UIView? / NSView? 渲染於標記下方的背景視圖
zoomRange ClosedRange<CGFloat> 最小/最大縮放比例
supportedFeatureSet FeatureSet 已啟用的 PaperKit 功能集

觸控模式

PaperMarkupViewController.TouchMode 包含兩種情況:.drawing 與 .selection。

paperVC.directTouchMode = .drawing    // 手指繪圖
paperVC.directTouchMode = .selection  // 手指選取元素
paperVC.directTouchAutomaticallyDraws = true  // 系統根據 Pencil 狀態自動決定

內容背景

可在標記層下方設定任何視圖,作為範本、文件頁面或欲進行標註的影像。請確保 PaperMarkup(bounds:) 的座標空間與背景內容(如 PDF 頁面或渲染影像尺寸)對齊,以便儲存的標註能在正確位置還原:

let pageBounds = CGRect(origin: .zero, size: pageImage.size)
let imageView = UIImageView(image: pageImage)
imageView.frame = pageBounds

let markup = PaperMarkup(bounds: pageBounds)
paperVC = PaperMarkupViewController(markup: markup, supportedFeatureSet: features)
paperVC.contentView = imageView

Delegate 回呼(Callbacks)

方法 呼叫時機
paperMarkupViewControllerDidChangeMarkup(_:) 標記內容發生變更時
paperMarkupViewControllerDidBeginDrawing(_:) 使用者開始繪圖時
paperMarkupViewControllerDidChangeSelection(_:) 選取內容發生變更時
paperMarkupViewControllerDidChangeContentVisibleFrame(_:) 可見框線範圍發生變更時

PaperMarkup 資料模型

PaperMarkup 是一個 Sendable 結構,用於儲存所有標記元素與 PencilKit 繪圖資料。

建立與持久化

// 建立新的空白模型。Bounds 用於定義儲存文件的座標空間。
let markup = PaperMarkup(bounds: CGRect(x: 0, y: 0, width: 612, height: 792))

// 從已儲存的資料載入
let markup = try PaperMarkup(dataRepresentation: savedData)

// 儲存 — dataRepresentation() 為 async throws
func save(_ markup: PaperMarkup) async throws {
    let data = try await markup.dataRepresentation()
    try data.write(to: fileURL)
}

以程式化方式插入內容

// 文字方塊
markup.insertNewTextbox(
    attributedText: AttributedString("Annotation"),
    frame: CGRect(x: 50, y: 100, width: 200, height: 40),
    rotation: 0
)

// 影像
markup.insertNewImage(cgImage, frame: CGRect(x: 50, y: 200, width: 300, height: 200), rotation: 0)

// 形狀
let shapeConfig = ShapeConfiguration(
    type: .rectangle,
    fillColor: UIColor.systemBlue.withAlphaComponent(0.2).cgColor,
    strokeColor: UIColor.systemBlue.cgColor,
    lineWidth: 2
)
markup.insertNewShape(configuration: shapeConfig, frame: CGRect(x: 50, y: 420, width: 200, height: 100), rotation: 0)

// 附帶箭頭端點標記的線條
let lineConfig = ShapeConfiguration(type: .line, fillColor: nil, strokeColor: UIColor.red.cgColor, lineWidth: 3)
markup.insertNewLine(
    configuration: lineConfig,
    from: CGPoint(x: 50, y: 550), to: CGPoint(x: 250, y: 550),
    startMarker: false, endMarker: true
)

形狀型別:.rectangle、.roundedRectangle、.ellipse、.line、.arrowShape、.star、.chatBubble、.regularPolygon。

其他操作

markup.append(contentsOf: otherMarkup)       // 合併另一個 PaperMarkup
markup.append(contentsOf: pkDrawing)          // 合併 PKDrawing
markup.transformContent(CGAffineTransform(...)) // 套用仿射轉換 (Affine transform)
markup.removeContentUnsupported(by: featureSet) // 移除不被 featureSet 支援的元素
屬性 說明
bounds 標記的座標空間
contentsRenderFrame 包覆所有內容的緊密邊界框(Bounding box)
featureSet 此資料模型內容所使用的功能集
indexableContent 可擷取用於搜尋索引的文字

可在視圖控制器上使用 suggestedFrameForInserting(contentInFrame:) 來取得能避免與既有內容重疊的建議 Frame。

插入控制器

MarkupEditViewController (iOS, iPadOS, Mac Catalyst, visionOS)

顯示彈出式功能表(Popover menu),用於插入形狀、文字方塊、線條及其他元素。

func showInsertionMenu(from barButtonItem: UIBarButtonItem) {
    let editVC = MarkupEditViewController(
        supportedFeatureSet: paperVC.supportedFeatureSet,
        additionalActions: []
    )
    editVC.delegate = paperVC  // PaperMarkupViewController 符合此 delegate 協定
    editVC.modalPresentationStyle = .popover
    editVC.popoverPresentationController?.barButtonItem = barButtonItem
    present(editVC, animated: true)
}

MarkupToolbarViewController (macOS, Mac Catalyst)

提供包含繪圖工具與插入按鈕的工具列。適用於原生 macOS 以及 Mac Catalyst 工具列風格的 UI;若 Mac Catalyst 應用程式想使用 UIKit Popover,可以使用 MarkupEditViewController。

let toolbar = MarkupToolbarViewController(supportedFeatureSet: paperVC.supportedFeatureSet)
toolbar.delegate = paperVC
addChild(toolbar)
toolbar.view.frame = toolbarContainerView.bounds
toolbarContainerView.addSubview(toolbar.view)
toolbar.didMove(toParent: self)

這兩個控制器都必須使用與 PaperMarkupViewController 相同的 FeatureSet。

FeatureSet 設定

FeatureSet 可控制要啟用哪些標記功能。

預設組合 說明
.latest 目前的所有功能 — 建議採用的起始設定
.version1 版本 1 所包含的功能
.empty 不啟用任何功能

自訂

var features = FeatureSet.latest
features.remove(.stickers)
features.remove(.images)

// 或從空集合開始組建
var features = FeatureSet.empty
features.insert(.drawing)
features.insert(.text)
features.insert(.shapeStrokes)

可用功能

功能 說明
.drawing 自由形式 PencilKit 繪圖
.text 插入文字方塊
.images 插入影像
.stickers 插入貼圖
.links 連結標註
.loupes 放大鏡元素
.shapeStrokes 形狀外框
.shapeFills 形狀填色
.shapeOpacity 形狀透明度控制

HDR 支援

將 FeatureSet 與 PKToolPicker 上的 colorMaximumLinearExposure 設定為高於 1.0 的數值:

var features = FeatureSet.latest
features.colorMaximumLinearExposure = 4.0
toolPicker.colorMaximumLinearExposure = features.colorMaximumLinearExposure

使用 view.window?.windowScene?.screen.potentialEDRHeadroom 來配合裝置螢幕的支援能力。若僅支援 SDR,請使用 1.0。

形狀、墨水與線條端點標記

features.shapes = [.rectangle, .ellipse, .arrowShape, .line]
features.inks = [.pen, .pencil, .marker]
features.lineMarkerPositions = .all  // .single, .double, .plain, 或 .all

與 PencilKit 整合

PaperKit 接受使用 PKTool 進行繪圖,且可以附加 PKDrawing 內容。

當應用程式依賴自訂筆刷行為、原始 PKDrawing / PKStroke 分析,或自訂以套索為中心(Lasso-centric)的編輯時,PaperKit 並不能直接作為低階 PKCanvasView 的無縫替代方案。請讓 PencilKit 繼續維護這些工作流程,並在其旁加入 PaperKit,以實現結構化的審閱標記,例如圖解說明(Callouts)、箭頭、文字方塊、標籤、影像印章及系統標準的插入 UI。只有當低階編輯路徑不再需要擁有該內容時,才將既有繪圖轉移或複製到 PaperKit 標註層(使用 PaperMarkup.append(contentsOf: PKDrawing))。

import Penci

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