paperkit

paperkit

热门

使用 PaperKit 轻松添加手绘、形状以及一致的标记(Markup)体验。适用于集成 PaperMarkupViewController 进行标记编辑、添加形状识别、处理 PaperMarkup 数据模型、在文档编辑器中嵌入标记工具,或构建需要系统标准标记工具栏的批注功能。iOS 26 新增功能。

967Star
49Fork
更新于 2026/7/31
SKILL.md
只读
名称
paperkit
描述

使用 PaperKit 轻松添加手绘、形状以及一致的标记(Markup)体验。适用于集成 PaperMarkupViewController 进行标记编辑、添加形状识别、处理 PaperMarkup 数据模型、在文档编辑器中嵌入标记工具,或构建需要系统标准标记工具栏的批注功能。iOS 26 新增功能。

PaperKit

注意(Beta 阶段 API)。 PaperKit 是 iOS/iPadOS 26、macOS 26 及 visionOS 26 的新增框架。API 仍有可能调整,发布前请务必参考 Apple 最新的官方文档确认细节。

由 PaperMarkupViewController 管理的画布中,PaperKit 将 PencilKit 自由手绘与形状、文本、图片、线条等结构化标记元素融合在一起。

目录

工作流程

  1. 构建 UI 之前,先确定文档边界 (bounds)、支持的 FeatureSet 以及持久化版本。
  2. 创建 PaperMarkup,嵌入 PaperMarkupViewController,并在视图生命周期内保持 Controller、Tool Picker 以及 Insertion Controller 处于存活状态。
  3. 使用适配对应平台的插入界面,并将 PencilKit 手绘限制在 PaperKit 文档边界之内。
  4. 在主线程之外执行保存操作;对于向前不兼容的内容保留缩略图;使用相同的 feature set 测试完整读写加载 (round-trip loading)。
  5. 若加载失败,还原原始文档字节数据,修复 feature-set/版本/Controller 的不匹配问题,并重新运行“编辑、保存、重启、加载、缩略图降级回滚与撤销”检测流程。

加载 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

交互式标记的核心 View Controller。提供支持滚动的画布,用于 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 特性集

触摸模式(Touch Modes)

PaperMarkupViewController.TouchMode 包含两种枚举值:.drawing(绘画)和 .selection(选择)。

paperVC.directTouchMode = .drawing    // 手指滑动进行手绘
paperVC.directTouchMode = .selection  // 手指点击选择元素
paperVC.directTouchAutomaticallyDraws = true  // 系统根据 Apple Pencil 状态自动决定

内容背景

可以在标记图层下方设置任意 View,用作模板、文档页面或待标注的图片。确保 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 回调方法

方法 触发时机
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(...)) // 应用仿射变换
markup.removeContentUnsupported(by: featureSet) // 剥离不支持的元素
属性 说明
bounds 标记的坐标空间
contentsRenderFrame 包含所有内容的紧凑包围盒 (Bounding box)
featureSet 当前数据模型内容所使用的特性集
indexableContent 可提取用于搜索索引的文本

可在 View Controller 上调用 suggestedFrameForInserting(contentInFrame:) 来获取一个避免与现有内容重叠的放置 Frame。

插入控制器(Insertion Controllers)

MarkupEditViewController (iOS, iPadOS, Mac Catalyst, visionOS)

弹出一个 Popover 菜单,用于插入形状、文本框、线条和其他元素。

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;如果 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 数据分析或自定义套索编辑,PaperKit 并不是底层 PKCanvasView 的直接替代品。在这些场景下,应继续由 PencilKit 负责底层手绘工作流程,并在旁边引入 PaperKit,以实现结构化的评审标记功能(如标注框、箭头、文本框、标签、图片印章以及系统标准的插入 UI)。只有当底层编辑链路不再需要单独接管该手绘内容时,才建议使用 PaperMarkup.append(contentsOf: PKDrawing) 将已有绘制迁移或复制到 PaperKit 标记图层中。

import Penci

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