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 自由手绘和结构化标记元素。遵循 ObservablePKToolPickerObserver 协议。

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 支持

FeatureSetPKToolPicker 上将 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 -->