MCPInstall

paperkit

Add 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 that need the system-standard markup toolbar. New in iOS 26.

Installs
3.1K
Last 24h
+15
Trending rank
#7015
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/dpearson2699/swift-ios-skills --skill paperkit
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads paperkit automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

PaperKit

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.

PaperKit combines PencilKit drawing with structured markup elements such as shapes, text, images, and lines in a canvas managed by PaperMarkupViewController.

Contents

Workflow

  1. Choose the document bounds, supported FeatureSet, and persistence version before constructing UI.
  2. Create PaperMarkup, embed PaperMarkupViewController, and keep the controller, tool picker, and insertion controller alive for the view lifetime.
  3. Use the platform-appropriate insertion surface and keep PencilKit drawing inside the PaperKit document boundary.
  4. Save off the main thread, retain a thumbnail for forward-incompatible content, and test round-trip loading with the same feature set.
  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.

Load references/paperkit-patterns.md for full platform setup, tool picker wiring, persistence, thumbnails, custom feature sets, programmatic construction, and migration.

Setup

PaperKit requires no entitlements or special Info.plist entries.

import PaperKit

Platform availability: iOS 26.0+, iPadOS 26.0+, Mac Catalyst 26.0+, macOS 26.0+, visionOS 26.0+.

Three core components:

ComponentRole
PaperMarkupViewControllerInteractive canvas for creating and displaying markup and drawing
PaperMarkupData model for serializing all markup elements and PencilKit drawing
MarkupEditViewController / MarkupToolbarViewControllerInsertion UI for adding markup elements

PaperMarkupViewController

The primary view controller for interactive markup. Provides a scrollable canvas for freeform PencilKit drawing and structured markup elements. Conforms to Observable and PKToolPickerObserver.

Basic UIKit Setup

import PaperKit
import PencilKit
import UIKit

class MarkupViewController: UIViewController, PaperMarkupViewController.Delegate {
    var paperVC: PaperMarkupViewController!
    var toolPicker: PKToolPicker!

    override func viewDidLoad() {
        super.viewDidLoad()

        let pageBounds = CGRect(origin: .zero, size: CGSize(width: 612, height: 792))
        let markup = PaperMarkup(bounds: pageBounds)
        let features = FeatureSet.latest

        paperVC = PaperMarkupViewController(
            markup: markup,
            supportedFeatureSet: features
        )
        paperVC.delegate = self

        addChild(paperVC)
        paperVC.view.frame = view.bounds
        paperVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(paperVC.view)
        paperVC.didMove(toParent: self)

        toolPicker = PKToolPicker()
        toolPicker.addObserver(paperVC)
        paperVC.pencilKitResponderState.activeToolPicker = toolPicker
        paperVC.pencilKitResponderState.toolPickerVisibility = .visible
    }

    func paperMarkupViewControllerDidChangeMarkup(
        _ controller: PaperMarkupViewController
    ) {
        guard let markup = controller.markup else { return }
        Task { try await save(markup) }
    }
}

Key Properties

PropertyTypeDescription
markupPaperMarkup?The current data model
selectedMarkupPaperMarkupCurrently selected content
isEditableBoolWhether the canvas accepts input
isRulerActiveBoolWhether the ruler overlay is shown
drawingToolany PKToolActive PencilKit drawing tool
contentViewUIView? / NSView?Background view rendered beneath markup
zoomRangeClosedRange<CGFloat>Min/max zoom scale
supportedFeatureSetFeatureSetEnabled PaperKit features

Touch Modes

PaperMarkupViewController.TouchMode has two cases: .drawing and .selection.

paperVC.directTouchMode = .drawing    // Finger draws
paperVC.directTouchMode = .selection  // Finger selects elements
paperVC.directTouchAutomaticallyDraws = true  // System decides based on Pencil state

Content Background

Set any view beneath the markup layer for templates, document pages, or images being annotated. Keep the PaperMarkup(bounds:) coordinate space aligned to the background content, such as a PDF page or rendered image size, so saved annotations restore in the right place:

let pageBounds = CGRect(origin: .zero, size: pageImage.size)
let imageView = UIImageView(image: pageImage)
imageView.frame = pageBounds

let markup = PaperMarkup(bounds: pageBounds)
paperVC = PaperMarkupViewController(markup: markup, supportedFeatureSet: features)
paperVC.contentView = imageView

Delegate Callbacks

MethodCalled when
paperMarkupViewControllerDidChangeMarkup(_:)Markup content changes
paperMarkupViewControllerDidBeginDrawing(_:)User starts drawing
paperMarkupViewControllerDidChangeSelection(_:)Selection changes
paperMarkupViewControllerDidChangeContentVisibleFrame(_:)Visible frame changes

PaperMarkup Data Model

PaperMarkup is a Sendable struct that stores all markup elements and PencilKit drawing data.

Creating and Persisting

// New empty model. Bounds define the saved document coordinate space.
let markup = PaperMarkup(bounds: CGRect(x: 0, y: 0, width: 612, height: 792))

// Load from saved data
let markup = try PaperMarkup(dataRepresentation: savedData)

// Save — dataRepresentation() is async throws
func save(_ markup: PaperMarkup) async throws {
    let data = try await markup.dataRepresentation()
    try data.write(to: fileURL)
}

Inserting Content Programmatically

// Text box
markup.insertNewTextbox(
    attributedText: AttributedString("Annotation"),
    frame: CGRect(x: 50, y: 100, width: 200, height: 40),
    rotation: 0
)

// Image
markup.insertNewImage(cgImage, frame: CGRect(x: 50, y: 200, width: 300, height: 200), rotation: 0)

// Shape
let shapeConfig = 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)

// Line with arrow end marker
let lineConfig = 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
)

Shape types: .rectangle, .roundedRectangle, .ellipse, .line, .arrowShape, .star, .chatBubble, .regularPolygon.

Other Operations

markup.append(contentsOf: otherMarkup)       // Merge another PaperMarkup
markup.append(contentsOf: pkDrawing)          // Merge a PKDrawing
markup.transformContent(CGAffineTransform(...)) // Apply affine transform
markup.removeContentUnsupported(by: featureSet) // Strip unsupported elements
PropertyDescription
boundsCoordinate space of the markup
contentsRenderFrameTight bounding box of all content
featureSetFeatures used by this data model's content
indexableContentExtractable text for search indexing

Use suggestedFrameForInserting(contentInFrame:) on the view controller to get a frame that avoids overlapping existing content.

Insertion Controllers

MarkupEditViewController (iOS, iPadOS, Mac Catalyst, visionOS)

Presents a popover menu for inserting shapes, text boxes, lines, and other elements.

func showInsertionMenu(from barButtonItem: UIBarButtonItem) {
    let editVC = MarkupEditViewController(
        supportedFeatureSet: paperVC.supportedFeatureSet,
        additionalActions: []
    )
    editVC.delegate = paperVC  // PaperMarkupViewController conforms to the delegate
    editVC.modalPresentationStyle = .popover
    editVC.popoverPresentationController?.barButtonItem = barButtonItem
    present(editVC, animated: true)
}

MarkupToolbarViewController (macOS, Mac Catalyst)

Provides a toolbar with drawing tools and insertion buttons. Use it for native macOS and for Mac Catalyst toolbar-style UI; Catalyst apps that want a UIKit popover can use MarkupEditViewController.

let toolbar = MarkupToolbarViewController(supportedFeatureSet: paperVC.supportedFeatureSet)
toolbar.delegate = paperVC
addChild(toolbar)
toolbar.view.frame = toolbarContainerView.bounds
toolbarContainerView.addSubview(toolbar.view)
toolbar.didMove(toParent: self)

Both controllers must use the same FeatureSet as the PaperMarkupViewController.

FeatureSet Configuration

FeatureSet controls which markup capabilities are available.

PresetDescription
.latestAll current features — recommended starting point
.version1Features from version 1
.emptyNo features enabled

Customizing

var features = FeatureSet.latest
features.remove(.stickers)
features.remove(.images)

// Or build up from empty
var features = FeatureSet.empty
features.insert(.drawing)
features.insert(.text)
features.insert(.shapeStrokes)

Available Features

FeatureDescription
.drawingFreeform PencilKit drawing
.textText box insertion
.imagesImage insertion
.stickersSticker insertion
.linksLink annotations
.loupesLoupe/magnifier elements
.shapeStrokesShape outlines
.shapeFillsShape fills
.shapeOpacityShape opacity control

HDR Support

Set colorMaximumLinearExposure above 1.0 on both the FeatureSet and PKToolPicker:

var features = FeatureSet.latest
features.colorMaximumLinearExposure = 4.0
toolPicker.colorMaximumLinearExposure = features.colorMaximumLinearExposure

Use view.window?.windowScene?.screen.potentialEDRHeadroom to match the device screen's capability. Use 1.0 for SDR-only.

Shapes, Inks, and Line Markers

features.shapes = [.rectangle, .ellipse, .arrowShape, .line]
features.inks = [.pen, .pencil, .marker]
features.lineMarkerPositions = .all  // .single, .double, .plain, or .all

Integration with PencilKit

PaperKit accepts PKTool for drawing and can append PKDrawing content.

PaperKit is not a drop-in replacement for a low-level PKCanvasView when the app depends on custom brush behavior, raw PKDrawing / PKStroke analytics, or custom lasso-centric editing. Keep those workflows owned by PencilKit, and add PaperKit beside them for structured review markup such as callouts, arrows, text boxes, labels, image stamps, and system-standard insertion UI. Migrate or duplicate existing drawings into a PaperKit annotation layer with PaperMarkup.append(contentsOf: PKDrawing) only when the low-level editing path no longer needs to own that content.

import PencilKit

// Set drawing tool
paperVC.drawingTool = PKInkingTool(.pen, color: .black, width: 3)

// Merge existing PKDrawing into markup
markup.append(contentsOf: existingPKDrawing)

Tool Picker Setup

let toolPicker = PKToolPicker()
toolPicker.addObserver(paperVC)
paperVC.pencilKitResponderState.activeToolPicker = toolPicker
paperVC.pencilKitResponderState.toolPickerVisibility = .visible

Setting toolPickerVisibility to .hidden keeps the picker functional (responds to Pencil gestures) but not visible, enabling the mini tool picker experience.

Content Version Compatibility

FeatureSet.ContentVersion maps to PKContentVersion:

let pkVersion = features.contentVersion.pencilKitContentVersion

SwiftUI Integration

Wrap PaperMarkupViewController in UIViewControllerRepresentable:

struct MarkupView: UIViewControllerRepresentable {
    @Binding var markup: PaperMarkup
    let features: FeatureSet

    func makeUIViewController(context: Context) -> PaperMarkupViewController {
        let vc = PaperMarkupViewController(markup: markup, supportedFeatureSet: features)
        vc.delegate = context.coordinator
        let toolPicker = PKToolPicker()
        toolPicker.addObserver(vc)
        vc.pencilKitResponderState.activeToolPicker = toolPicker
        vc.pencilKitResponderState.toolPickerVisibility = .visible
        context.coordinator.toolPicker = toolPicker
        return vc
    }

    func updateUIViewController(_ vc: PaperMarkupViewController, context: Context) {
        if vc.markup != markup { vc.markup = markup }
    }

    func makeCoordinator() -> Coordinator { Coordinator(parent: self) }

    class Coordinator: NSObject, PaperMarkupViewController.Delegate {
        let parent: MarkupView
        var toolPicker: PKToolPicker?
        init(parent: MarkupView) { self.parent = parent }

        func paperMarkupViewControllerDidChangeMarkup(
            _ controller: PaperMarkupViewController
        ) {
            if let markup = controller.markup { parent.markup = markup }
        }
    }
}

Initialize the bound PaperMarkup from the document or page size before creating the SwiftUI bridge:

struct DocumentMarkupScreen: View {
    let pageSize: CGSize
    @State private var markup: PaperMarkup
    private let features = FeatureSet.latest

    init(pageSize: CGSize) {
        self.pageSize = pageSize
        _markup = State(
            initialValue: PaperMarkup(
                bounds: CGRect(origin: .zero, size: pageSize)
            )
        )
    }

    var body: some View {
        MarkupView(markup: $markup, features: features)
    }
}

Common Mistakes

MistakeFix
View, insertion UI, and saved document use mismatched feature setsChoose one supported FeatureSet and use it across the editing session.
Loaded content is assigned without a version checkVerify markup.featureSet.isSubset(of: supportedFeatureSet) or show the saved thumbnail/fallback.
Serialization blocks UI or overlaps unsafelyAwait dataRepresentation() off the interaction path and debounce autosaves.
Tool picker is a local variableRetain it for the controller/view lifetime.
Wrong insertion surface for the platformUse MarkupToolbarViewController on macOS; use MarkupEditViewController on UIKit, with Catalyst supporting either presentation.

Review Checklist

  • import PaperKit present; deployment target is iOS 26+ / macOS 26+ / visionOS 26+
  • PaperMarkup initialized with bounds matching content size
  • Same FeatureSet used for PaperMarkupViewController and insertion controller
  • dataRepresentation() called in async context
  • PKToolPicker retained as a stored property
  • Delegate set on PaperMarkupViewController for change callbacks
  • Content version checked when loading saved data
  • Correct insertion controller per platform (MarkupToolbarViewController for macOS/Catalyst toolbar UI; MarkupEditViewController for UIKit/Catalyst popovers)
  • MarkupError cases handled on deserialization
  • HDR: colorMaximumLinearExposure set on FeatureSet and PKToolPicker.colorMaximumLinearExposure

References

FAQ

What does the paperkit skill do?

Add 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 that need the system-standard markup toolbar. New in iOS 26.

How do I install paperkit?

Run npx skills add https://github.com/dpearson2699/swift-ios-skills --skill paperkit in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/paperkit/ (personal) or .claude/skills/paperkit/ (project).

Which AI agents can use paperkit?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is paperkit safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains paperkit?

It is published by dpearson2699 in dpearson2699/swift-ios-skills. It has 3,071 recorded installs.

More from dpearson2699/swift-ios-skills

View all →

swiftui-animation

dpearson2699/swift-ios-skills

Implement, diagnose, or review SwiftUI motion using explicit and scoped implicit animations, springs, transitions, PhaseAnimator, KeyframeAnimator, matched geometry or navigation zoom, SF Symbol effects, and custom Animation types. Use when views should animate on state changes, insertion, removal, navigation, or multi-step choreography, or when motion must respect Reduce Motion and Swift concurrency.

Mobile5.5K installs

ios-accessibility

dpearson2699/swift-ios-skills

Build and audit SwiftUI, UIKit, and AppKit accessibility for VoiceOver, Voice Control, Switch Control, Full Keyboard Access, Dynamic Type, focus restoration, labels/traits/actions, traversal, custom rotors, NSAccessibility, XCTest checks, adaptive system preferences, and App Store accessibility declarations. Use when implementing accessible UI, fixing an accessibility audit, testing assistive-technology behavior, or substantiating App Store Accessibility Nutrition Labels.

Mobile5K installs

swiftui-performance

dpearson2699/swift-ios-skills

Profile, diagnose, and remediate SwiftUI runtime performance using code review, Instruments, and repeatable measurements. Use when a SwiftUI screen renders slowly, scrolling or animations hitch, view bodies update excessively, list identity churns, layout work spikes, or broad Observation dependencies raise CPU cost. Covers evidence-based triage, SwiftUI Instruments lanes, lazy-container guardrails, state lifetime, and before/after verification.

Mobile4.6K installs

swiftui-patterns

dpearson2699/swift-ios-skills

Builds and reviews SwiftUI views with modern MV architecture, state, composition, isolated previews, and migration guidance. Covers @Observable ownership, @State/@Bindable/@Environment wiring, view decomposition, ViewModifiers, environment values, .task loading, iOS 26+ handoffs, Writing Tools, clipboard availability, and performance. Use when structuring SwiftUI state, managing @Observable, composing views, previewing meaningful UI states, or correcting SwiftUI patterns.

Mobile4.6K installs

app-store-review

dpearson2699/swift-ios-skills

Audits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.

Mobile4.6K installs

ios-networking

dpearson2699/swift-ios-skills

Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps.

Mobile4.4K installs

Popular mobile skills

View category →

design-mobile-apps

designed-by-ai/skills

Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.

Mobile1.1M installs

ios-design

uizze.sh

Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.

Mobile416.5K installs

vercel-react-native-skills

vercel-labs/agent-skills

React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.

OfficialMobile256.7K installs

animate-expo

emilkowalski/skills

Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.

Mobile140.1K installs

xcode-project-setup

firebase/agent-skills

Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).

OfficialMobile131.2K installs

firebase-remote-config-basics

firebase/agent-skills

Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.

OfficialMobile121.7K installs