Meet PaperKit
App Services 入门 1m

认识 PaperKit

Meet PaperKit

2025年6月9日

在 Apple 官方观看视频

一句话判断

PaperKit 把系统级的标记体验(Notes、截图、QuickLook 里的那个)开放给了第三方 App——画笔 + 形状 + 图片 + 文本框全在一个画布上,macOS Tahoe 也支持了。上手成本极低,但 FeatureSet 的定制和前向兼容性的处理才是真正拉开差距的地方。

这场 Session 讲了什么

PaperKit 是 Apple 自己的标记框架,之前只在系统 App 里用(Notes、截图、QuickLook、Journal)。现在第三方 App 可以直接集成。

三个核心组件:PaperMarkupViewController 是标记控制器,负责交互式创建和展示标记/PencilKit 绘画;PaperMarkup 是数据模型容器,处理保存/加载/渲染;MarkupEditViewController(iOS/iPadOS/visionOS)和 MarkupToolbarViewController(macOS)是元素插入 UI。

集成步骤极简:初始化 PaperMarkup(配置 bounds)-> 创建 PaperMarkupViewController(传入 FeatureSet)-> 加入视图层级 -> 配置 PencilKit Tool Picker。macOS 的区别只是插入 UI 用工具栏而非弹出菜单。

关键新功能:macOS Tahoe 原生支持 PaperKit;PencilKit 新增了书法笔(Reed 工具);HDR 支持(通过 colorMaximumLinearExposure 属性);FeatureSet.latest 自动获取最新功能;可以设置自定义背景视图(contentView)。

值得深挖的点

FeatureSet 的”latest”陷阱

FeatureSet.latest 给你所有最新功能,但这是双刃剑。如果 Apple 在后续系统版本往 latest 里加了新工具,你的 App 会自动获得这些工具——对大部分场景是好事。但如果你的 App 面向特定用户群体(比如教育、企业),你可能不想让新工具不受控地出现。建议的做法是:从 latest 出发,用 remove 去掉不需要的工具,而不是从空集用 insert 构建——这样未来的新工具默认可用,你可以根据需要选择性关闭。

另一个细节:FeatureSet 需要同时赋值给 PaperMarkupViewController 和 MarkupEditViewController/MarkupToolbarViewController。如果只给控制器没给插入 UI,用户可能看到工具却无法使用。

前向兼容性是硬性要求

Session 特别强调了版本检查。PaperMarkup 数据包含版本号,如果高版本系统创建的标记在低版本 App 里打开,可能会出现不兼容。最佳实践是在加载时检查 contentVersion,如果不匹配就显示预渲染的缩略图。

缩略图的生成方式:创建 CGContext -> 调用 PaperMarkup 的 draw 方法渲染到上下文 -> 保存缩略图。这是 Notes App 实际使用的方式。

HDR 标记的实现细节

HDR 效果通过 colorMaximumLinearExposure 控制。设成 1 是 SDR,大于 1 是 HDR。实际的 HDR 亮度取决于设备屏幕支持的 HDR headroom——可以用 UIScreen.main.maximumExtendedDynamicRangeColorComponentValue 获取屏幕支持的最大值。Session 的 demo 用了 4,效果比较好。

HDR 对 Reed 书法笔的效果提升很明显——笔触的渐变和光泽在 HDR 下更有层次感。

代码片段

iOS 基础集成

class MarkupViewController: UIViewController {
    var markup: PaperMarkup!
    var markupController: PaperMarkupViewController!

    override func viewDidLoad() {
        super.viewDidLoad()

        // 初始化数据模型
        markup = PaperMarkup(bounds: view.bounds)

        // 创建标记控制器
        let featureSet = FeatureSet.latest
        markupController = PaperMarkupViewController(markup: markup,
                                                      featureSet: featureSet)
        addChild(markupController)
        view.addSubview(markupController.view)
        markupController.didMove(toParent: self)

        // 配置 PencilKit 工具选择器
        let toolPicker = PencilKitToolPicker()
        toolPicker.add(markupController)

        // 工具选择器可见性控制
        markupController.pencilKitResponderState.toolPickerVisibility = .visible
    }
}

坑:PaperMarkup 的 bounds 必须和视图的 bounds 一致——不匹配会导致渲染偏移或裁切。

macOS 工具栏集成

// macOS 的区别是用工具栏而非弹出菜单
class MacMarkupViewController: NSViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let markup = PaperMarkup(bounds: view.bounds)
        let featureSet = FeatureSet.latest
        let markupController = PaperMarkupViewController(markup: markup,
                                                           featureSet: featureSet)

        // macOS 用工具栏
        let toolbar = MarkupToolbarViewController(featureSet: featureSet)
        toolbar.delegate = markupController

        // 标准视图控制器嵌入流程
        addChild(markupController)
        view.addSubview(markupController.view)
    }
}

HDR + 自定义背景 + 前向兼容

// HDR 支持
var featureSet = FeatureSet.latest
featureSet.colorMaximumLinearExposure = 4.0

// 自定义背景(比如食谱模板)
let templateView = UIImageView(image: UIImage(named: "recipe_template"))
markupController.contentView = templateView

// 前向兼容处理
func loadMarkup(data: Data) {
    do {
        markup = try PaperMarkup(data: data)
    } catch {
        // 版本不兼容,显示缩略图
        displayThumbnail(of: data)
    }
}

func displayThumbnail(of data: Data) {
    let context = CGContext(/* 配置 */)
    let markup = PaperMarkup(data: data)
    markup.draw(in: context!)
    let thumbnail = UIImage(cgImage: context!.makeImage()!)
    imageView.image = thumbnail
}

坑:draw 方法在版本不兼容时仍然可以工作——它会用支持的最高版本来渲染。但你不能编辑。

最佳实践

从 FeatureSet.latest 出发做减法。不要从空集开始逐个 insert——维护成本太高,而且会错过未来的新功能。用 remove 只去掉不适合你 App 的工具。如果你的 App 不需要 HDR 标记,就不要设 colorMaximumLinearExposure。

SwiftUI 集成用 UIViewControllerRepresentable 包装。PaperKit 的组件都是 UIKit 的,但封装后可以直接用在 SwiftUI 视图层级里。保持 UIKit 组件和 SwiftUI 布局的兼容性。

PencilKit Tool Picker 的新 API(pencilKitResponderState)可以控制工具选择器的可见性——即使隐藏了工具选择器,双击和挤压手势仍然有效。这对想要自定义工具栏 UI 的 App 非常有用。

还有什么值得关注

  • PaperKit 支持的标记元素包括:自由绘画、形状(包括星星评分这类用法)、图片(支持拖放)、文本框等。
  • Reed 书法笔是 PencilKit 的新工具,在 HDR 下效果最佳。
  • PaperMarkup 的 delegate 提供了 markupChange 回调,可以在这里自动保存。另外 PaperMarkupViewController 实现了 Observable,也可以用 Observation 框架监听变化。
  • SwiftUI 集成时,用 UIViewControllerRepresentable 包装后可以在 SwiftUI body 里直接使用,保留 UIKit 和 SwiftUI 两种框架的兼容性。
  • macOS Tahoe 上 PaperKit 的体验和 iOS 一致——包括形状、图片、文本框等所有标记元素,Journal App 就是用 PaperKit 构建的。
应用服务