본문으로 건너뛰기
cccskills
무료GitHub에 공개

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.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(2)

  • SKILL.md14.7 KB
  • references/paperkit-patterns.md17.3 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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 provides a unified markup experience — the same framework powering markup in Notes, Screenshots, QuickLook, and Journal. It combines PencilKit drawing with structured markup elements (shapes, text boxes, images, lines) in a single canvas managed by PaperMarkupViewController. Requires Swift 6.3 and the iOS 26+ SDK.

Contents

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 markup = PaperMarkup(bounds: view.bounds)
        paperVC = PaperMarkupViewController(
            markup: markup,
            supportedFeatureSet: .latest
        )
        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:

paperVC.contentView = UIImageView(image: UIImage(named: "template"))

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
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, visionOS)

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

func showInsertionMenu(from barButtonItem: UIBarButtonItem) {
    let editVC = MarkupEditViewController(
        supportedFeatureSet: .latest,
        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.

let toolbar = MarkupToolbarViewController(supportedFeatureSet: .latest)
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.maximumLinearExposure = 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.

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

    func makeUIViewController(context: Context) -> PaperMarkupViewController {
        let vc = PaperMarkupViewController(markup: markup, supportedFeatureSet: .latest)
        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 }
        }
    }
}

Common Mistakes

Mismatched FeatureSets

// DON'T
let paperVC = PaperMarkupViewController(markup: m, supportedFeatureSet: .latest)
let editVC = MarkupEditViewController(supportedFeatureSet: .version1, additionalActions: [])

// DO — use the same FeatureSet for both
let features = FeatureSet.latest
let paperVC = PaperMarkupViewController(markup: m, supportedFeatureSet: features)
let editVC = MarkupEditViewController(supportedFeatureSet: features, additionalActions: [])

Ignoring Content Version on Load

// DON'T
let markup = try PaperMarkup(dataRepresentation: data)
paperVC.markup = markup

// DO — check version compatibility
let markup = try PaperMarkup(dataRepresentation: data)
if markup.featureSet.isSubset(of: paperVC.supportedFeatureSet) {
    paperVC.markup = markup
} else {
    showVersionMismatchAlert()
}

Blocking Main Thread with Serialization

// DON'T — dataRepresentation() is async, don't try to work around it

// DO — save from an async context
func paperMarkupViewControllerDidChangeMarkup(_ controller: PaperMarkupViewController) {
    guard let markup = controller.markup else { return }
    Task {
        let data = try await markup.dataRepresentation()
        try data.write(to: fileURL)
    }
}

Forgetting to Retain the Tool Picker

// DON'T — local variable gets deallocated
func viewDidLoad() {
    let toolPicker = PKToolPicker()
    toolPicker.addObserver(paperVC)
}

// DO — store as instance property
var toolPicker: PKToolPicker!

Wrong Insertion Controller for Platform

// DON'T — MarkupEditViewController is iOS/iPadOS/visionOS only

// DO
#if os(macOS)
let toolbar = MarkupToolbarViewController(supportedFeatureSet: features)
#else
let editVC = MarkupEditViewController(supportedFeatureSet: features, additionalActions: [])
#endif

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 (MarkupEditViewController vs MarkupToolbarViewController)
  • MarkupError cases handled on deserialization
  • HDR: colorMaximumLinearExposure set on both FeatureSet and PKToolPicker

References

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

When the user wants to A/B test App Store product page elements to improve conversion rate. Also use when the user mentions "A/B test", "product page optimization", "test my screenshots", "test my icon", "conversion rate optimization", "CPP", or "custom product pages". For screenshot design, see screenshot-optimization. For metadata optimization, see metadata-optimization.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

Discover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

alarmkit

무료

Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, and Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, AlarmButton stop and snooze actions, authorization, state observation, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

When the user wants to optimize their Google Play Store listing — title, short description, full description, keywords, ratings, or Play Store-specific features. Use when the user mentions "Google Play", "Android", "Play Store", "Play Console", "short description", "full description indexed", "Google Play ASO", or wants Google Play-specific keyword, creative, or ratings strategy. For iOS App Store optimization, see aso-audit and metadata-optimization.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

JordanCoin의 스킬 모두 보기

이 스킬의 문제 신고