mcpField
P
paperkit

paperkit

github.com/dpearson2699ドキュメント1.2k3.1k 回インストール13 週間前に更新
リポジトリ

paperkit について

Appleプラットフォームの開発者向けに、PencilKitの描画と構造化マークアップを組み合わせたキャンバスの構築方法を解説するPaperKitガイドです。データ保存、機能セットの設定、UI連携を扱い、Apple 26の各プラットフォームで利用できますが、APIは変更される可能性があります。

主な機能

  1. 01構造化マークアップの描画と編集
  2. 02テキスト、図形、画像の挿入
  3. 03対応する機能セットの設定
  4. 04マークアップデータの保存と読み込み
  5. 05PencilKitおよびSwiftUIとの連携

活用シーン

  1. 01文書ページに描画や注釈を追加
  2. 02テキストボックス、矢印、ラベルで内容をレビュー
  3. 03キャンバスの内容を保存して後から復元

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/paperkitを入力して読み込まれたか確認します。

同梱ファイルevals/evals.jsonreferences/paperkit-patterns.md

詳細

PaperKit

ベータ版に関する注意事項。 PaperKit は iOS/iPadOS 26、macOS 26、visionOS 26 で新たに追加されました。API の範囲は変更される可能性があります。リリース前に、最新の Apple のドキュメントで詳細を確認してください。

PaperKit は、PaperMarkupViewController が管理するキャンバスで、PencilKit の描画と、図形、テキスト、画像、線などの構造化されたマークアップ要素を組み合わせます。

目次

ワークフロー

  1. UI を構築する前に、ドキュメントの境界、サポートする FeatureSet、永続化バージョンを選択します。
  2. PaperMarkup を作成して PaperMarkupViewController を組み込み、ビューのライフタイムを通じてコントローラー、ツールピッカー、挿入コントローラーを保持します。
  3. プラットフォームに適した挿入 UI を使用し、PencilKit の描画が PaperKit ドキュメントの境界内に収まるようにします。
  4. メインスレッド以外で保存し、将来のバージョンとの互換性がないコンテンツに備えてサムネイルを保持し、同じフィーチャーセットで往復読み込みをテストします。
  5. 失敗した場合は、元のドキュメントのバイト列を復元し、フィーチャーセット、バージョン、コントローラー間の不一致を修正したうえで、編集、保存、再起動、読み込み、サムネイルへのフォールバック、取り消しを再テストします。

プラットフォームごとの完全なセットアップ、ツールピッカーの接続、永続化、サムネイル、カスタムフィーチャーセット、プログラムによる構築、移行については、references/paperkit-patterns.md を参照してください。

セットアップ

PaperKit に必要なエンタイトルメントや特別な Info.plist エントリーはありません。

import PaperKit

プラットフォームの対応状況: iOS 26.0 以降、iPadOS 26.0 以降、Mac Catalyst 26.0 以降、macOS 26.0 以降、visionOS 26.0 以降。

主なコンポーネントは 3 つです。

コンポーネント役割
PaperMarkupViewControllerマークアップと描画を作成、表示するためのインタラクティブなキャンバス
PaperMarkupすべてのマークアップ要素と PencilKit の描画をシリアライズするデータモデル
MarkupEditViewController / MarkupToolbarViewControllerマークアップ要素を追加するための挿入 UI

PaperMarkupViewController

インタラクティブなマークアップを扱うための主要なビューコントローラーです。フリーフォームの PencilKit 描画と構造化されたマークアップ要素を扱える、スクロール可能なキャンバスを提供します。Observable と PKToolPickerObserver に準拠しています。

UIKit の基本的なセットアップ
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) }
    }
}
主なプロパティ
プロパティ型説明
markupPaperMarkup?現在のデータモデル
selectedMarkupPaperMarkup現在選択されているコンテンツ
isEditableBoolキャンバスが入力を受け付けるかどうか
isRulerActiveBool定規オーバーレイを表示するかどうか
drawingToolany PKToolアクティブな PencilKit の描画ツール
contentViewUIView? / NSView?マークアップの下に描画される背景ビュー
zoomRangeClosedRange<CGFloat>ズーム倍率の最小値と最大値
supportedFeatureSetFeatureSet有効になっている PaperKit の機能
タッチモード

PaperMarkupViewController.TouchMode には .drawing と .selection の 2 つのケースがあります。

paperVC.directTouchMode = .drawing    // Finger draws
paperVC.directTouchMode = .selection  // Finger selects elements
paperVC.directTouchAutomaticallyDraws = true  // System decides based on Pencil state
コンテンツの背景

テンプレート、ドキュメントのページ、注釈を付ける画像などのために、マークアップレイヤーの下に任意のビューを設定できます。保存した注釈を正しい位置に復元できるよう、PaperMarkup(bounds:) の座標空間を PDF ページやレンダリング済み画像のサイズなど、背景コンテンツと一致させます。

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
デリゲートコールバック
メソッド呼び出されるタイミング
paperMarkupViewControllerDidChangeMarkup(_:)マークアップコンテンツが変更されたとき
paperMarkupViewControllerDidBeginDrawing(_:)ユーザーが描画を開始したとき
paperMarkupViewControllerDidChangeSelection(_:)選択内容が変更されたとき
paperMarkupViewControllerDidChangeContentVisibleFrame(_:)表示フレームが変更されたとき

PaperMarkup データモデル

PaperMarkup は、すべてのマークアップ要素と PencilKit の描画データを保存する Sendable 構造体です。

作成と永続化
// 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)
}
コンテンツのプログラムによる挿入
// 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
)

図形の種類:.rectangle、.roundedRectangle、.ellipse、.line、.arrowShape、.star、.chatBubble、.regularPolygon。

その他の操作
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
プロパティ説明
boundsマークアップの座標空間
contentsRenderFrameすべてのコンテンツを囲む最小の境界ボックス
featureSetこのデータモデルのコンテンツで使用される機能
indexableContent検索インデックス作成用に抽出できるテキスト

既存のコンテンツと重ならないフレームを取得するには、ビューコントローラーで suggestedFrameForInserting(contentInFrame:) を使用します。

挿入コントローラー

MarkupEditViewController(iOS、iPadOS、Mac Catalyst、visionOS)

図形、テキストボックス、線、その他の要素を挿入するためのポップオーバーメニューを表示します。

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)

描画ツールと挿入ボタンを備えたツールバーを提供します。ネイティブ macOS およびツールバー形式の UI を使用する Mac Catalyst 向けです。UIKit のポップオーバーを使用する Catalyst アプリでは、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)

両方のコントローラーには、PaperMarkupViewController と同じ FeatureSet を使用する必要があります。

FeatureSet の設定

FeatureSet は、利用可能なマークアップ機能を制御します。

プリセット説明
.latest現在のすべての機能。最初に使用する設定として推奨
.version1バージョン 1 の機能
.empty機能を有効にしない
カスタマイズ
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)
利用可能な機能
機能説明
.drawing自由形式の PencilKit 描画
.textテキストボックスの挿入
.images画像の挿入
.stickersステッカーの挿入
.linksリンク注釈
.loupesルーペ/拡大鏡の要素
.shapeStrokes図形の輪郭
.shapeFills図形の塗りつぶし
.shapeOpacity図形の不透明度の制御
HDR サポート

FeatureSet と PKToolPicker の両方で colorMaximumLinearExposure を 1.0 より大きく設定します。

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

デバイス画面の性能に合わせるには、view.window?.windowScene?.screen.potentialEDRHeadroom を使用します。SDR のみの場合は 1.0 を使用します。

図形、インク、線のマーカー
features.shapes = [.rectangle, .ellipse, .arrowShape, .line]
features.inks = [.pen, .pencil, .marker]
features.lineMarkerPositions = .all  // .single, .double, .plain, or .all

PencilKit との統合

PaperKit は描画に PKTool を使用でき、PKDrawing のコンテンツを追加できます。

アプリがカスタムブラシの動作、生の PKDrawing/PKStroke の分析、またはカスタムの投げ縄中心の編集に依存している場合、PaperKit は低レベルの PKCanvasView のそのまま置き換えられるものではありません。これらのワークフローは PencilKit に任せたまま、コールアウト、矢印、テキストボックス、ラベル、画像スタンプ、システム標準の挿入 UI など、構造化されたレビュー用マークアップには PaperKit を併用してください。低レベルの編集経路で既存の描画を管理する必要がなくなった場合に限り、PaperMarkup.append(contentsOf: PKDrawing) を使用して、既存の描画を PaperKit の注釈レイヤーに移行または複製してください。

import PencilKit

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

// Merge existing PKDrawing into markup
markup.append(contentsOf: existingPKDrawing)
ツールピッカーの設定
let toolPicker = PKToolPicker()
toolPicker.addObserver(paperVC)
paperVC.pencilKitResponderState.activeToolPicker = toolPicker
paperVC.pencilKitResponderState.toolPickerVisibility = .visible

toolPickerVisibility を .hidden に設定すると、ピッカーは機能したまま(Apple Pencil のジェスチャに応答したまま)非表示になり、ミニツールピッカーを利用できます。

コンテンツバージョンの互換性

FeatureSet.ContentVersion は PKContentVersion に対応します。

let pkVersion = features.contentVersion.pencilKitContentVersion

SwiftUI との統合

PaperMarkupViewController を 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 }
        }
    }
}

SwiftUI のブリッジを作成する前に、ドキュメントまたはページサイズを基に、バインドされた PaperMarkup を初期化します。

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)
    }
}

よくある間違い

間違い修正方法
ビュー、挿入 UI、保存したドキュメントで機能セットが一致していないサポート対象の FeatureSet を 1 つ選び、編集セッション全体で使用します。
バージョンを確認せずに読み込んだコンテンツを割り当てているmarkup.featureSet.isSubset(of: supportedFeatureSet) を確認するか、保存済みのサムネイル/フォールバックを表示します。
シリアル化によって UI がブロックされる、または安全でない方法で重複して実行されるインタラクションの処理経路外で dataRepresentation() を待機し、自動保存をデバウンスします。
ツールピッカーをローカル変数にしているコントローラー/ビューのライフタイム中は保持します。
プラットフォームに合わない挿入サーフェスを使用しているmacOS では MarkupToolbarViewController を、UIKit では MarkupEditViewController を使用します。Catalyst ではどちらの表示方法もサポートされています。

レビューチェックリスト

  • import PaperKit が記述されている。デプロイメントターゲットは iOS 26+ / macOS 26+ / visionOS 26+
  • PaperMarkup がコンテンツサイズに一致する境界で初期化されている
  • PaperMarkupViewController と挿入コントローラーで同じ FeatureSet を使用している
  • dataRepresentation() が非同期コンテキストで呼び出されている
  • PKToolPicker が保存プロパティとして保持されている
  • 変更コールバックのために PaperMarkupViewController にデリゲートが設定されている
  • 保存データの読み込み時にコンテンツのバージョンを確認している
  • プラットフォームに合った挿入コントローラーを使用している(macOS/Catalyst のツールバー UI では MarkupToolbarViewController、UIKit/Catalyst のポップオーバーでは MarkupEditViewController)
  • デシリアライズ時に MarkupError の各ケースを処理している
  • HDR:FeatureSet と PKToolPicker.colorMaximumLinearExposure に colorMaximumLinearExposure が設定されている

参考資料

コメント