Highlight
PaperKit 是 Apple 内部用于 Notes、Preview、Freeform 等应用的画布引擎,iOS、macOS 和 visionOS 27 起向开发者开放,提供完整的画布数据模型、元素操作和自定义装饰层能力。
核心内容
以前想要在 App 里做一个可画、可标记、可拖拽图片的画布,需要自己处理触摸事件、绘制逻辑、撤销重做、手势识别、图层管理……工作量巨大。做出来的体验也很难和系统原生应用一致。
PaperKit 就是 Apple 用来解决这个问题的统一框架。它用完代替简单的绘图工具,整的画布体验——铅笔、形状、文本、图片全部协同工作。你在 Notes 里画草图、在 Preview 里签名高亮、在 Freeform 里头脑风暴,背后都是 PaperKit。
这个框架的核心有三个层次:
数据模型层:PaperMarkup 暴露了画布上所有元素的有序集合 subelements,你可以读取、创建、修改任何元素。
元素控制层:每个元素都遵循 Markup 协议,有统一的 frame、rotation 属性,也有各自特定的属性。新增的 allowedInteractions 让你精确控制每个元素可以被哪些方式操作——移动、调整大小、旋转、删除、样式修改、选择,都可以独立控制。
装饰层(Adornments):在画布之上叠加临时 UI 控件,比如按钮、注释、协作标记。这些控件不会成为文档的一部分,不会被保存或导出,只在编辑时存在,自动跟随缩放和滚动。
详细内容
数据模型与 subelements
PaperKit 的核心是 PaperMarkup,它代表整个画布的内容。([01:36](https://developer.apple.com/videos/play/wwdc2026/372/?time=96))
import PaperKit
func generateMarkup(pageSize: CGSize, panelFrames: [CGRect], configuration: ShapeConfiguration) -> PaperMarkup {
var markup = PaperMarkup(bounds: CGRect(origin: .zero, size: pageSize))
var subelements: MarkupOrderedSet = markup.subelements
for panelFrame: CGRect in panelFrames {
let shape = ShapeMarkup(frame: panelFrame, configuration: configuration)
subelements.append(shape)
}
markup.subelements = subelements
return markup
}
关键点:
PaperMarkup初始化时需要bounds,定义画布大小subelements是MarkupOrderedSet类型,有序集合,可以读写ShapeMarkup是形状元素,初始化需要位置frame和配置configuration- 修改
subelements后赋值回markup.subelements才会生效
控制元素交互行为
默认情况下,画布上的元素可以自由选择、拖拽、删除。但某些场景需要锁定模板元素。([03:03](https://developer.apple.com/videos/play/wwdc2026/372/?time=183))
import PaperKit
func generateMarkup(pageSize: CGSize, panelFrames: [CGRect], configuration: ShapeConfiguration) -> PaperMarkup {
var markup = PaperMarkup(bounds: CGRect(origin: .zero, size: pageSize))
var subelements: MarkupOrderedSet = markup.subelements
for panelFrame: CGRect in panelFrames {
var shape = ShapeMarkup(frame: panelFrame, configuration: configuration)
shape.allowedInteractions = .readOnly
subelements.append(shape)
}
markup.subelements = subelements
return markup
}
关键点:
allowedInteractions是MarkupInteractions选项集合.readOnly是组合标志,一次性禁用移动、调整大小、旋转、删除、样式修改和选择- 也可以单独控制,如只允许移动、不允许删除
样式修改
所有元素都有其类型特定的属性。ShapeMarkup 有 strokeColor 和 fillColor。([04:22](https://developer.apple.com/videos/play/wwdc2026/372/?time=262))
import PaperKit
func updatePanelColor(_ selectedColor: CGColor) {
guard var markup: PaperMarkup = paperMarkupViewController.markup else { return }
var subelements: MarkupOrderedSet = markup.subelements
for element in subelements {
guard var shape = element as? ShapeMarkup else { continue }
shape.strokeColor = selectedColor
shape.fillColor = selectedColor.copy(alpha: 0.15)
subelements.updateOrAppend(shape)
}
markup.subelements = subelements
markup.backgroundColor = selectedColor.copy(alpha: 0.15)
paperMarkupViewController.markup = markup
}
关键点:
- 迭代
subelements时需要类型转换,因为集合里是各种Markup协议类型 updateOrAppend方法会更新已存在的元素或添加新元素- 修改完成后需要重新赋值给
paperMarkupViewController.markup
装饰层(Adornments)
Adornments 是叠加在画布上的临时控件,不会被保存到文档中。适合做按钮、注释、协作 UI。([05:53](https://developer.apple.com/videos/play/wwdc2026/372/?time=353))
import PaperKit
func addPanelAdornments(for page: Page) {
var adornments: [MarkupAdornment] = []
for (panelIndex, panel) in page.panels.enumerated() {
let adornmentID = UUID()
adornmentPanelMapping[adornmentID] = panelIndex
let center = CGPoint(x: panel.midX, y: panel.midY)
let adornment = MarkupAdornment(
id: adornmentID,
anchor: .canvas(location: center),
imageConfiguration: .systemImage("photo.badge.plus"),
dragRegion: .fixed,
scalesWithZoom: false
)
adornments.append(adornment)
}
paperMarkupViewController.adornments = adornments
}
关键点:
MarkupAdornment用UUID唯一标识,用于后续处理点击事件anchor用.canvas(location:)锚定到画布坐标,会自动跟随缩放滚动imageConfiguration支持 SF SymbolsdragRegion: .fixed表示按钮位置固定,scalesWithZoom: false表示不随缩放放大
处理装饰层点击
import ImagePlayground
import PaperKit
func paperMarkupViewController(_ paperMarkupViewController: PaperMarkupViewController, didTapAdornmentWithID id: UUID) {
guard let panelIndex = adornmentPanelMapping[id] else { return }
activeImageGenerationPanelIndex = panelIndex
let imagePlaygroundViewController = ImagePlaygroundViewController()
imagePlaygroundViewController.delegate = self
present(imagePlaygroundViewController, animated: true)
}
关键点:
- 实现代理方法接收装饰层点击事件
- 通过
id映射到具体的业务对象(如漫画面板索引) - 可以弹出其他界面(如 Image Playground)
插入生成的图片
import ImagePlayground
import PaperKit
func imageViewController(_ imageViewController: ImagePlaygroundViewController, didCreateImageAt imageURL: URL) {
guard let panelFrame = activeGenerationPanelFrame,
let paperMarkupViewController = pageViewController.paperViewController,
var markup = paperMarkupViewController.markup,
let image = UIImage(contentsOfFile: imageURL.path) else { return }
let imageMarkup = ImageMarkup(frame: panelFrame, image: image)
markup.subelements.append(imageMarkup)
paperMarkupViewController.markup = markup
}
关键点:
ImageMarkup用于创建图片元素,需要位置和 UIImage- 直接
append到subelements即可插入 - 更新
markup会触发视图刷新
与 PencilKit 的关系
PaperKit 基于 PencilKit 构建,支持 Apple Pencil 绘图。每个笔画都会成为 markup 元素,可以使用 PencilKit 的模型 API。这些 API 现在支持字符识别和贝塞尔曲线路径转换。
核心启发
漫画创作 App:像 demo 那样做漫画编辑器,用 allowedInteractions 锁定面板布局,用 adornments 添加”生成图片”按钮,结合 Image Playground 让用户快速创作。入口:ShapeMarkup + MarkupAdornment。
填色书 App:预置线稿模板(read-only),用户只能在指定区域填色。通过 subelements 迭代找到特定形状,修改 fillColor。
批注协作工具:用 adornments 显示协作者的在线状态、光标位置、评论标记。这些信息不会保存到文档,但实时更新。入口:MarkupAdornment + 定时刷新。
手写笔记搜索:利用 PencilKit 的字符识别 API,把手写内容转成文本,然后插入 TextMarkup 元素,让笔记可搜索。
模板填充工具:创建各种文档模板(发票、合同、表单),用 readOnly 锁定模板结构,用户只能填写空白区域。
关联 Session
- 203 - PencilKit 进阶 — PaperKit 基于 PencilKit,字符识别和贝塞尔路径转换的详情
- 375 - Create high-quality images using Image Playground — 如何在 App 中集成 Image Playground 生成图片
- 269 - SwiftUI — PaperKit 视图控制器与 SwiftUI 的集成方式
- 241 - Foundation Models — Apple 的基础模型能力,可与画布内容结合
- 277 - WidgetKit — 可以将画布内容渲染为 Widget 展示
评论
GitHub Issues · utterances