$npx -y skills add dpearson2699/swift-ios-skills --skill paperkitAdd drawings, shapes, and a consistent markup experience using PaperKit. Use when integrating PaperMarkupViewController for markup editing, adding shape recognition, working with PaperMarkup data models, embedding markup tools in document editors, or building annotation features
| 1 | # PaperKit |
| 2 | |
| 3 | > **Beta-sensitive.** PaperKit is new in iOS/iPadOS 26, macOS 26, and visionOS 26. API surface may change. Verify details against current Apple documentation before shipping. |
| 4 | |
| 5 | PaperKit combines PencilKit drawing with structured markup elements such as shapes, text, images, and lines in a canvas managed by `PaperMarkupViewController`. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | - [Setup](#setup) |
| 10 | - [Workflow](#workflow) |
| 11 | - [PaperMarkupViewController](#papermarkupviewcontroller) |
| 12 | - [PaperMarkup Data Model](#papermarkup-data-model) |
| 13 | - [Insertion Controllers](#insertion-controllers) |
| 14 | - [FeatureSet Configuration](#featureset-configuration) |
| 15 | - [Integration with PencilKit](#integration-with-pencilkit) |
| 16 | - [SwiftUI Integration](#swiftui-integration) |
| 17 | - [Common Mistakes](#common-mistakes) |
| 18 | - [Review Checklist](#review-checklist) |
| 19 | - [References](#references) |
| 20 | |
| 21 | ## Workflow |
| 22 | |
| 23 | 1. Choose the document bounds, supported `FeatureSet`, and persistence version before constructing UI. |
| 24 | 2. Create `PaperMarkup`, embed `PaperMarkupViewController`, and keep the controller, tool picker, and insertion controller alive for the view lifetime. |
| 25 | 3. Use the platform-appropriate insertion surface and keep PencilKit drawing inside the PaperKit document boundary. |
| 26 | 4. Save off the main thread, retain a thumbnail for forward-incompatible content, and test round-trip loading with the same feature set. |
| 27 | 5. On failure, restore the original document bytes, fix the feature-set/version/controller mismatch, and rerun edit, save, relaunch, load, thumbnail fallback, and undo checks. |
| 28 | |
| 29 | Load [references/paperkit-patterns.md](references/paperkit-patterns.md) for full platform setup, tool picker wiring, persistence, thumbnails, custom feature sets, programmatic construction, and migration. |
| 30 | |
| 31 | ## Setup |
| 32 | |
| 33 | PaperKit requires no entitlements or special Info.plist entries. |
| 34 | |
| 35 | ```swift |
| 36 | import PaperKit |
| 37 | ``` |
| 38 | |
| 39 | **Platform availability:** iOS 26.0+, iPadOS 26.0+, Mac Catalyst 26.0+, macOS 26.0+, visionOS 26.0+. |
| 40 | |
| 41 | Three core components: |
| 42 | |
| 43 | | Component | Role | |
| 44 | |---|---| |
| 45 | | `PaperMarkupViewController` | Interactive canvas for creating and displaying markup and drawing | |
| 46 | | `PaperMarkup` | Data model for serializing all markup elements and PencilKit drawing | |
| 47 | | `MarkupEditViewController` / `MarkupToolbarViewController` | Insertion UI for adding markup elements | |
| 48 | |
| 49 | ## PaperMarkupViewController |
| 50 | |
| 51 | The primary view controller for interactive markup. Provides a scrollable canvas for freeform PencilKit drawing and structured markup elements. Conforms to `Observable` and `PKToolPickerObserver`. |
| 52 | |
| 53 | ### Basic UIKit Setup |
| 54 | |
| 55 | ```swift |
| 56 | import PaperKit |
| 57 | import PencilKit |
| 58 | import UIKit |
| 59 | |
| 60 | class MarkupViewController: UIViewController, PaperMarkupViewController.Delegate { |
| 61 | var paperVC: PaperMarkupViewController! |
| 62 | var toolPicker: PKToolPicker! |
| 63 | |
| 64 | override func viewDidLoad() { |
| 65 | super.viewDidLoad() |
| 66 | |
| 67 | let pageBounds = CGRect(origin: .zero, size: CGSize(width: 612, height: 792)) |
| 68 | let markup = PaperMarkup(bounds: pageBounds) |
| 69 | let features = FeatureSet.latest |
| 70 | |
| 71 | paperVC = PaperMarkupViewController( |
| 72 | markup: markup, |
| 73 | supportedFeatureSet: features |
| 74 | ) |
| 75 | paperVC.delegate = self |
| 76 | |
| 77 | addChild(paperVC) |
| 78 | paperVC.view.frame = view.bounds |
| 79 | paperVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] |
| 80 | view.addSubview(paperVC.view) |
| 81 | paperVC.didMove(toParent: self) |
| 82 | |
| 83 | toolPicker = PKToolPicker() |
| 84 | toolPicker.addObserver(paperVC) |
| 85 | paperVC.pencilKitResponderState.activeToolPicker = toolPicker |
| 86 | paperVC.pencilKitResponderState.toolPickerVisibility = .visible |
| 87 | } |
| 88 | |
| 89 | func paperMarkupViewControllerDidChangeMarkup( |
| 90 | _ controller: PaperMarkupViewController |
| 91 | ) { |
| 92 | guard let markup = controller.markup else { return } |
| 93 | Task { try await save(markup) } |
| 94 | } |
| 95 | } |
| 96 | ``` |
| 97 | |
| 98 | ### Key Properties |
| 99 | |
| 100 | | Property | Type | Description | |
| 101 | |---|---|---| |
| 102 | | `markup` | `PaperMarkup?` | The current data model | |
| 103 | | `selectedMarkup` | `PaperMarkup` | Currently selected content | |
| 104 | | `isEditable` | `Bool` | Whether the canvas accepts input | |
| 105 | | `isRulerActive` | `Bool` | Whether the ruler overlay is shown | |
| 106 | | `drawingTool` | `any PKTool` | Active PencilKit drawing tool | |
| 107 | | `contentView` | `UIView?` / `NSView?` | Background view rendered beneath markup | |
| 108 | | `zoomRange` | `ClosedRange<CGFloat>` | Min/max zoom scale | |
| 109 | | `supportedFeatureSet` | `FeatureSet` | Enabled PaperKit features | |
| 110 | |
| 111 | ### Touch Modes |
| 112 | |
| 113 | `PaperMarkupViewController.TouchMode` has two cases: `.drawing` and `.selection`. |
| 114 | |
| 115 | ```swift |
| 116 | paperVC.directTouchMode = .drawing // Finger draws |
| 117 | paperVC.directTouchMode = .s |