其他
在 iOS/iPadOS 26、macOS 26…
需要在 Apple 平台文档编辑器或标注功能中嵌入 PaperKit 画布、系统标注工具栏并安全持久化 PaperMarkup 数据时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 39/100 ×0.15
适用场景
在 iOS/iPadOS 26、macO特定
解决思路
- 设置:无需 entitlements 或特殊 Info.plist 条目;在代码中 `import PaperKit`。
- 设置:确认部署目标为 iOS 26.0+、iPadOS 26.0+、Mac Catalyst 26.0+、macOS 26.0+、visionOS 26.0+。
- 设置:使用三个核心组件:`PaperMarkupViewController` 作为交互画布,`PaperMarkup` 作为序列化数据模型,`MarkupEditViewController` / `MarkupToolbarViewController` 作为插入 UI。
- Workflow:在构造 UI 之前先选择文档 bounds、支持的 `FeatureSet` 和持久化版本。
- Workflow:创建 `PaperMarkup`,嵌入 `PaperMarkupViewController`,并在 view 生命周期内持有 controller、tool picker 和 insertion controller。
- Workflow:使用与平台匹配的插入界面,并让 PencilKit drawing 保持在 PaperKit 文档边界内。
- Workflow:在主线程外保存,保留 thumbnail 以应对向前不兼容内容,并用相同 feature set 测试 round-trip loading。
- Workflow:失败时恢复原始文档 bytes,修复 feature-set/version/controller 不匹配,然后重新运行 edit、save、relaunch、load、thumbnail fallback 和 undo 检查。
- Workflow:需要完整平台设置、tool picker wiring、持久化、缩略图、自定义 feature sets、程序化构造和迁移时,加载 `references/paperkit-patterns.md`。
- PaperMarkupViewController:UIKit 中设置:定义 `pageBounds`,创建 `PaperMarkup(bounds: pageBounds)` 和 `FeatureSet.latest`,用 `PaperMarkupViewController(markup:supportedFeatureSet:)` 初始化,设置 `delegate`。
- PaperMarkupViewController:将 `paperVC` 作为 child view controller:`addChild(paperVC)`,设置 `paperVC.view.frame = view.bounds` 和 `autoresizingMask = [.flexibleWidth, .flexibleHeight]`,`view.addSubview(paperVC.view)`,`paperVC.didMove(toParent: self)`。
- PaperMarkupViewController:创建 `PKToolPicker()`,调用 `toolPicker.addObserver(paperVC)`,设置 `paperVC.pencilKitResponderState.activeToolPicker = toolPicker` 和 `paperVC.pencilKitResponderState.toolPickerVisibility = .visible`。
- PaperMarkupViewController:实现 `paperMarkupViewControllerDidChangeMarkup(_:)`,从 controller 取 `markup`,在 `Task` 中异步 `save(markup)`。
- PaperMarkupViewController:通过设置 `markup`、`selectedMarkup`、`isEditable`、`isRulerActive`、`drawingTool`、`contentView`、`zoomRange`、`supportedFeatureSet` 控制画布当前数据、选择、可编辑性、标尺、绘图工具、背景内容、缩放范围和支持特性。
- PaperMarkupViewController:利用 `PaperMarkupViewController` 遵循 `Observable` 和 `PKToolPickerObserver` 的特性。
- Touch Modes:设置 `paperVC.directTouchMode = .drawing` 让手指绘图;设置 `.selection` 让手指选择元素;设置 `paperVC.directTouchAutomaticallyDraws = true` 让系统根据 Pencil 状态决定。
- Content Background:在 markup 层下方设置任意 view 作为模板、文档页或待标注图片:把该 view 赋给 `paperVC.contentView`。
- Content Background:让 `PaperMarkup(bounds:)` 坐标空间与背景内容(如 PDF 页或渲染图片尺寸)对齐,以确保保存的 annotations 恢复到正确位置。
- Content Background:示例:用 `let pageBounds = CGRect(origin: .zero, size: pageImage.size)`,创建 `UIImageView(image: pageImage)` 并设置 `imageView.frame = pageBounds`,创建 `PaperMarkup(bounds: pageBounds)`,初始化 controller 后设置 `paperVC.contentView = imageView`。
- Delegate:实现 `paperMarkupViewControllerDidChangeMarkup(_:)` 处理 markup 内容变化。
- Delegate:实现 `paperMarkupViewControllerDidBeginDrawing(_:)` 处理开始绘图。
- Delegate:实现 `paperMarkupViewControllerDidChangeSelection(_:)` 处理选择变化。
- Delegate:实现 `paperMarkupViewControllerDidChangeContentVisibleFrame(_:)` 处理可见 frame 变化。
- PaperMarkup 数据模型:把 `PaperMarkup` 视为 `Sendable` struct,用它存储所有 markup 元素和 PencilKit drawing 数据。
- PaperMarkup 数据模型:创建新空模型:`let markup = PaperMarkup(bounds: CGRect(x: 0, y: 0, width: 612, height: 792))`;bounds 定义保存文档坐标空间。
- PaperMarkup 数据模型:从保存数据加载:`let markup = try PaperMarkup(dataRepresentation: savedData)`。
- PaperMarkup 数据模型:保存:调用 `dataRepresentation()`(async throws),即 `let data = try await markup.dataRepresentation()`,再 `try data.write(to: fileURL)`。
- PaperMarkup 数据模型:程序化插入文本框:`markup.insertNewTextbox(attributedText: AttributedString("Annotation"), frame: CGRect(x: 50, y: 100, width: 200, height: 40), rotation: 0)`。
- PaperMarkup 数据模型:程序化插入图片:`markup.insertNewImage(cgImage, frame: CGRect(x: 50, y: 200, width: 300, height: 200), rotation: 0)`。
- PaperMarkup 数据模型:程序化插入形状:创建 `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)`。
- PaperMarkup 数据模型:程序化插入线:创建 `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)`。
- PaperMarkup 数据模型:可用形状类型:`.rectangle`、`.roundedRectangle`、`.ellipse`、`.line`、`.arrowShape`、`.star`、`.chatBubble`、`.regularPolygon`。
- PaperMarkup 数据模型:合并另一个 `PaperMarkup`:`markup.append(contentsOf: otherMarkup)`。
- PaperMarkup 数据模型:合并 `PKDrawing`:`markup.append(contentsOf: pkDrawing)`。
- PaperMarkup 数据模型:应用仿射变换:`markup.transformContent(CGAffineTransform(...))`。
- PaperMarkup 数据模型:移除当前 feature set 不支持的内容:`markup.removeContentUnsupported(by: featureSet)`。
- PaperMarkup 数据模型:使用数据模型属性:`bounds` 是坐标空间;`contentsRenderFrame` 是所有内容的紧致 bounding box;`featureSet` 是内容使用的特性;`indexableContent` 是可抽取用于搜索索引的文本。
- PaperMarkup 数据模型:使用 `suggestedFrameForInserting(contentInFrame:)` 获取避免与已有内容重叠的插入 frame。
- Insertion Controllers:在 iOS、iPadOS、Mac Catalyst、visionOS 用 `MarkupEditViewController(supportedFeatureSet:additionalActions:)` 呈现插入形状、文本框、线等元素的 popover 菜单。
- Insertion Controllers:`MarkupEditViewController` 设置 `editVC.delegate = paperVC`(`PaperMarkupViewController` 遵循对应 delegate),`editVC.modalPresentationStyle = .popover`,`editVC.popoverPresentationController?.barButtonItem = barButtonItem`,然后 `present(editVC, animated: true)`。
- Insertion Controllers:在 macOS 和 Mac Catalyst 用 `MarkupToolbarViewController(supportedFeatureSet:)` 提供带绘图工具和插入按钮的工具栏;Catalyst 想要 UIKit popover 时也可用 `MarkupEditViewController`。
- Insertion Controllers:添加 `MarkupToolbarViewController`:设置 `toolbar.delegate = paperVC`,`addChild(toolbar)`,设置 `toolbar.view.frame = toolbarContainerView.bounds`,`toolbarContainerView.addSubview(toolbar.view)`,`toolbar.didMove(toParent: self)`。
- Insertion Controllers:两个 insertion controller 必须使用与 `PaperMarkupViewController` 相同的 `FeatureSet`。
- FeatureSet:用 `FeatureSet` 控制可用 markup 能力;预设包括 `.latest`(所有当前特性,推荐起点)、`.version1`(version 1 特性)、`.empty`(不启用任何特性)。
- FeatureSet:自定义 feature set:从 `FeatureSet.latest` 开始 `features.remove(.stickers)`、`features.remove(.images)`;或从 `FeatureSet.empty` 开始 `features.insert(.drawing)`、`features.insert(.text)`、`features.insert(.shapeStrokes)`。
- FeatureSet:可用特性包括 `.drawing`(自由 PencilKit 绘图)、`.text`(文本框插入)、`.images`(图片插入)、`.stickers`(贴纸插入)、`.links`(链接标注)、`.loupes`(放大镜元素)、`.shapeStrokes`(形状轮廓)、`.shapeFills`(形状填充)、`.shapeOpacity`(形状不透明度控制)。
- FeatureSet HDR:在 `FeatureSet` 和 `PKToolPicker` 上都把 `colorMaximumLinearExposure` 设为大于 `1.0`,例如 `features.colorMaximumLinearExposure = 4.0`,`toolPicker.colorMaximumLinearExposure = features.colorMaximumLinearExposure`。
- FeatureSet HDR:匹配设备屏幕能力:使用 `view.window?.windowScene?.screen.potentialEDRHeadroom`;仅 SDR 时使用 `1.0`。
- FeatureSet:限制形状、墨水和线标记:设置 `features.shapes = [.rectangle, .ellipse, .arrowShape, .line]`,`features.inks = [.pen, .pencil, .marker]`,`features.lineMarkerPositions = .all`(也可用 `.single`、`.double`、`.plain`、`.all`)。
- PencilKit 集成:PaperKit 接受 `PKTool` 用于绘图,并可追加 `PKDrawing` 内容。
- PencilKit 集成:设置绘图工具:`paperVC.drawingTool = PKInkingTool(.pen, color: .black, width: 3)`。
- PencilKit 集成:把已有 `PKDrawing` 合并到 markup:`markup.append(contentsOf: existingPKDrawing)`。
- PencilKit 集成:Tool picker 设置:`let toolPicker = PKToolPicker()`,`toolPicker.addObserver(paperVC)`,`paperVC.pencilKitResponderState.activeToolPicker = toolPicker`,`paperVC.pencilKitResponderState.toolPickerVisibility = .visible`。
- PencilKit 集成:设置 `toolPickerVisibility` 为 `.hidden` 可让 picker 保持功能(响应 Pencil 手势)但不可见,实现 mini tool picker 体验。
- PencilKit 集成:内容版本兼容:`FeatureSet.ContentVersion` 映射到 `PKContentVersion`,用 `let pkVersion = features.contentVersion.pencilKitContentVersion`。
- PencilKit 集成:不要把 PaperKit 当作底层 `PKCanvasView` 的直接替代;当 app 依赖自定义笔刷行为、原始 `PKDrawing` / `PKStroke` 分析或自定义 lasso 编辑时,这些流程继续由 PencilKit 拥有。
- PencilKit 集成:在需要结构化 review markup(callouts、arrows、text boxes、labels、image stamps、system-standard insertion UI)时,把 PaperKit 加在 PencilKit 旁边。
- PencilKit 集成:仅当低层编辑路径不再需要拥有该内容时,才用 `PaperMarkup.append(contentsOf: PKDrawing)` 迁移或复制已有 drawings 到 PaperKit annotation layer。
- SwiftUI 集成:用 `UIViewControllerRepresentable` 包装 `PaperMarkupViewController`。
- SwiftUI 集成:在 `makeUIViewController(context:)` 中:创建 `PaperMarkupViewController(markup: markup, supportedFeatureSet: features)`,设置 `vc.delegate = context.coordinator`,创建 `PKToolPicker()`,`toolPicker.addObserver(vc)`,设置 `vc.pencilKitResponderState.activeToolPicker = toolPicker` 和 `vc.pencilKitResponderState.toolPickerVisibility = .visible`,把 `context.coordinator.toolPicker = toolPicker`,返回 `vc`。
- SwiftUI 集成:在 `updateUIViewController(_:context:)` 中:如果 `vc.markup != markup`,则 `vc.markup = markup`。
- SwiftUI 集成:在 `makeCoordinator()` 返回 `Coordinator`;Coordinator 继承 `NSObject` 并遵循 `PaperMarkupViewController.Delegate`,保存 `parent` 和 `toolPicker`;实现 `paperMarkupViewControllerDidChangeMarkup(_:)`,当 controller.markup 存在时把 `parent.markup = markup`。
- SwiftUI 集成:在创建 SwiftUI bridge 前,从 document 或 page size 初始化绑定的 `PaperMarkup`:`@State private var markup`,在 `init(pageSize:)` 中 `_markup = State(initialValue: PaperMarkup(bounds: CGRect(origin: .zero, size: pageSize)))`,body 使用 `MarkupView(markup: $markup, features: features)`。
- Common Mistakes:确保 view、insertion UI 和 saved document 使用匹配的 feature set:选择一个支持的 `FeatureSet` 并在整个编辑会话中统一使用。
- Common Mistakes:加载内容时不要直接赋值而不做版本检查:用 `markup.featureSet.isSubset(of: supportedFeatureSet)` 验证,或显示保存的 thumbnail/fallback。
- Common Mistakes:不要阻塞 UI 或让序列化重叠:在交互路径之外 await `dataRepresentation()`,并对 autosaves 做 debounce。
- Common Mistakes:不要把 tool picker 声明为局部变量:将其作为 stored property 持有到 controller/view 生命周期结束。
- Common Mistakes:为平台选择正确的 insertion surface:macOS 用 `MarkupToolbarViewController`;UIKit 用 `MarkupEditViewController`;Catalyst 两种呈现都支持。
- Review Checklist:确认 `import PaperKit` 存在,部署目标为 iOS 26+ / macOS 26+ / visionOS 26+。
- Review Checklist:确认 `PaperMarkup` 用与内容尺寸匹配的 bounds 初始化。
- Review Checklist:确认 `PaperMarkupViewController` 和 insertion controller 使用相同 `FeatureSet`。
- Review Checklist:确认在 async context 中调用 `dataRepresentation()`。
- Review Checklist:确认 `PKToolPicker` 作为 stored property 被持有。
- Review Checklist:确认在 `PaperMarkupViewController` 上设置 delegate 以接收变更回调。
- Review Checklist:确认加载保存数据时检查 content version。
- Review Checklist:确认按平台选择正确 insertion controller(macOS/Catalyst toolbar UI 用 `MarkupToolbarViewController`;UIKit/Catalyst popovers 用 `MarkupEditViewController`)。
- Review Checklist:确认在反序列化时处理 `MarkupError` cases。
- Review Checklist:HDR:确认在 `FeatureSet` 和 `PKToolPicker.colorMaximumLinearExposure` 上设置 `colorMaximumLinearExposure`。
适用边界 · 注意事项
- 当应用依赖底层 `PKCanvasView` 的自定义笔刷行为、原始 `PKDrawing` / `PKStroke` 分析或自定义套索编辑时,不要把 PaperKit 当作直接替代;这些流程继续由 PencilKit 负责,仅在旁边增加 PaperKit 用于结构化审阅标注。
- PaperKit 在 iOS/iPadOS 26、macOS 26、visionOS 26 才可用且属于 Beta-sensitive,API 可能变化;未确认当前 Apple 文档和部署目标前不要用于生产发布。
- 如果只需要简单 PencilKit 绘图、不需要系统标准标注插入 UI/形状/文本/图片等结构化标记,不必引入 PaperKit。