paperkit について
Appleプラットフォームの開発者向けに、PencilKitの描画と構造化マークアップを組み合わせたキャンバスの構築方法を解説するPaperKitガイドです。データ保存、機能セットの設定、UI連携を扱い、Apple 26の各プラットフォームで利用できますが、APIは変更される可能性があります。
主な機能
- 01構造化マークアップの描画と編集
- 02テキスト、図形、画像の挿入
- 03対応する機能セットの設定
- 04マークアップデータの保存と読み込み
- 05PencilKitおよびSwiftUIとの連携
活用シーン
- 01文書ページに描画や注釈を追加
- 02テキストボックス、矢印、ラベルで内容をレビュー
- 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 の描画と、図形、テキスト、画像、線などの構造化されたマークアップ要素を組み合わせます。
目次
- セットアップ
- ワークフロー
- PaperMarkupViewController
- PaperMarkup データモデル
- 挿入コントローラー
- FeatureSet の設定
- PencilKit との統合
- SwiftUI との統合
- よくある間違い
- 確認チェックリスト
- 参考資料
ワークフロー
- UI を構築する前に、ドキュメントの境界、サポートする
FeatureSet、永続化バージョンを選択します。 PaperMarkupを作成してPaperMarkupViewControllerを組み込み、ビューのライフタイムを通じてコントローラー、ツールピッカー、挿入コントローラーを保持します。- プラットフォームに適した挿入 UI を使用し、PencilKit の描画が PaperKit ドキュメントの境界内に収まるようにします。
- メインスレッド以外で保存し、将来のバージョンとの互換性がないコンテンツに備えてサムネイルを保持し、同じフィーチャーセットで往復読み込みをテストします。
- 失敗した場合は、元のドキュメントのバイト列を復元し、フィーチャーセット、バージョン、コントローラー間の不一致を修正したうえで、編集、保存、再起動、読み込み、サムネイルへのフォールバック、取り消しを再テストします。
プラットフォームごとの完全なセットアップ、ツールピッカーの接続、永続化、サムネイル、カスタムフィーチャーセット、プログラムによる構築、移行については、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) }
}
}
主なプロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
markup | PaperMarkup? | 現在のデータモデル |
selectedMarkup | PaperMarkup | 現在選択されているコンテンツ |
isEditable | Bool | キャンバスが入力を受け付けるかどうか |
isRulerActive | Bool | 定規オーバーレイを表示するかどうか |
drawingTool | any PKTool | アクティブな PencilKit の描画ツール |
contentView | UIView? / NSView? | マークアップの下に描画される背景ビュー |
zoomRange | ClosedRange<CGFloat> | ズーム倍率の最小値と最大値 |
supportedFeatureSet | FeatureSet | 有効になっている 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が設定されている
参考資料
- PaperKit のドキュメント
- PaperKit をアプリに統合する
- PaperKit の紹介 — WWDC25
pencilkitスキルでは、PencilKit による描画、ツールピッカー、PKDrawing のシリアル化を扱います- references/paperkit-patterns.md — データの永続化、レンダリング、マルチプラットフォームの設定、カスタム機能セット