Highlight
PaperKit is the canvas engine Apple uses internally in apps such as Notes, Preview, and Freeform. Starting in iOS, macOS, and visionOS 27, it is available to developers with a full canvas data model, element manipulation, and custom adornment layers.
Core Ideas
In the past, building an app canvas that supported drawing, markup, and draggable images meant handling touch events, drawing logic, undo and redo, gesture recognition, layer management, and much more. The implementation work was large, and the result was still hard to make feel consistent with Apple’s built-in apps.
PaperKit is Apple’s unified framework for solving that problem. It replaces simple drawing tools with a complete canvas experience where pencils, shapes, text, and images all work together. When you sketch in Notes, sign and highlight in Preview, or brainstorm in Freeform, PaperKit is behind the experience.
The framework has three main layers:
Data model layer: PaperMarkup exposes the ordered collection of all elements on the canvas through subelements, so you can read, create, and modify any element.
Element control layer: Every element conforms to the Markup protocol and has shared properties such as frame and rotation, plus type-specific properties. The new allowedInteractions API lets you precisely control how each element can be manipulated. Moving, resizing, rotating, deleting, changing style, and selecting can all be controlled independently.
Adornment layer: Add temporary UI controls above the canvas, such as buttons, notes, and collaboration markers. These controls do not become part of the document, are not saved or exported, and exist only during editing. They automatically follow zooming and scrolling.
Details
Data model and subelements
At the center of PaperKit is PaperMarkup, which represents the full contents of the canvas. ([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
}
Key points:
PaperMarkupneedsboundswhen initialized, which defines the canvas size.subelementsis a readable and writable ordered collection of typeMarkupOrderedSet.ShapeMarkupis a shape element. It needs aframeandconfigurationwhen initialized.- After changing
subelements, assign it back tomarkup.subelementsfor the change to take effect.
Controlling element interactions
By default, elements on the canvas can be selected, dragged, and deleted freely. Some scenarios need template elements to be locked. ([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
}
Key points:
allowedInteractionsis an option set of typeMarkupInteractions..readOnlyis a combined flag that disables moving, resizing, rotating, deleting, style changes, and selection all at once.- You can also control individual behaviors, such as allowing movement while disallowing deletion.
Style changes
All elements have type-specific properties. ShapeMarkup has strokeColor and 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
}
Key points:
- When iterating over
subelements, cast each item because the collection contains different types that conform toMarkup. updateOrAppendupdates an existing element or appends a new one.- After the update is complete, reassign the result to
paperMarkupViewController.markup.
Adornments
Adornments are temporary controls overlaid on the canvas. They are not saved into the document, so they are a good fit for buttons, annotations, and collaboration 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
}
Key points:
MarkupAdornmentuses aUUIDas a unique identifier for later click handling.anchoruses.canvas(location:)to bind to canvas coordinates, so it follows zooming and scrolling automatically.imageConfigurationsupports SF Symbols.dragRegion: .fixedmeans the button position is fixed, andscalesWithZoom: falsemeans it does not grow with zoom.
Handling adornment taps
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)
}
Key points:
- Implement the delegate method to receive adornment tap events.
- Map the
idback to a specific business object, such as a comic panel index. - Present another interface when needed, such as Image Playground.
Inserting the generated image
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
}
Key points:
ImageMarkupcreates an image element and needs both a position and aUIImage.- Append it directly to
subelementsto insert it. - Updating
markuprefreshes the view.
Relationship with PencilKit
PaperKit is built on PencilKit and supports Apple Pencil drawing. Every stroke becomes a markup element and can use PencilKit’s model APIs. These APIs now support character recognition and Bezier curve path conversion.
Key Takeaways
Comic creation app: Build a comic editor like the demo. Use allowedInteractions to lock the panel layout, add a “Generate Image” button with adornments, and combine it with Image Playground so users can create quickly. Entry point: ShapeMarkup + MarkupAdornment.
Coloring book app: Provide prebuilt line-art templates as read-only content, letting users color only specified regions. Iterate through subelements to find specific shapes and modify fillColor.
Collaborative annotation tool: Use adornments to show collaborators’ presence, cursor positions, and comment markers. This information is not saved into the document but can update in real time. Entry point: MarkupAdornment + periodic refresh.
Handwritten note search: Use PencilKit’s character recognition APIs to convert handwritten content to text, then insert TextMarkup elements so notes become searchable.
Template filling tool: Create document templates such as invoices, contracts, and forms. Lock the template structure with readOnly, and let users fill only the blank areas.
Related Sessions
- 203 - Go further with PencilKit — PaperKit is built on PencilKit; learn more about character recognition and Bezier path conversion
- 375 - Create high-quality images using Image Playground — How to integrate Image Playground image generation into your app
- 269 - SwiftUI — How to integrate a PaperKit view controller with SwiftUI
- 241 - Foundation Models — Apple’s foundation model capabilities, which can be combined with canvas content
- 277 - WidgetKit — Render canvas content for display in widgets
Comments
GitHub Issues · utterances