WWDC Quick Look 💓 By SwiftGGTeam
PaperKit をひも解く

PaperKit をひも解く

元の動画を見る

ハイライト

PaperKit は、Apple が Notes、Preview、Freeform などの App で内部的に使っているキャンバスエンジンです。iOS、macOS、visionOS 27 以降、開発者にも公開され、完全なキャンバスデータモデル、要素操作、カスタム装飾レイヤーを利用できます。

主要内容

以前、App 内に描画、マークアップ、画像のドラッグに対応したキャンバスを作ろうとすると、タッチイベント、描画ロジック、Undo/Redo、ジェスチャ認識、レイヤー管理などを自前で扱う必要がありました。作業量は大きく、完成した体験もシステム標準 App と同じ品質にそろえるのは難しいものでした。

PaperKit は、この問題を解決するための Apple の統一フレームワークです。単純な描画ツールの代わりに、ペン、図形、テキスト、画像が一体として動く完全なキャンバス体験を提供します。Notes でスケッチを描く、Preview で署名やハイライトを追加する、Freeform でブレインストーミングする。こうした体験の背後には PaperKit があります。

このフレームワークの中核は3つの層に分かれます。

データモデル層PaperMarkup はキャンバス上のすべての要素を順序付きコレクション subelements として公開します。任意の要素を読み取り、作成し、変更できます。

要素制御層:各要素は Markup プロトコルに準拠し、共通の framerotation プロパティと、型ごとの固有プロパティを持ちます。新しい allowedInteractions を使うと、各要素に対して移動、サイズ変更、回転、削除、スタイル変更、選択を個別に制御できます。

装飾レイヤー(Adornments):ボタン、注釈、共同作業マーカーなどの一時的な UI コントロールをキャンバス上に重ねられます。これらのコントロールはドキュメントの一部にならず、保存や書き出しもされません。編集時だけ存在し、ズームやスクロールに自動追従します。

詳細

データモデルと subelements

PaperKit の中核は、キャンバス全体の内容を表す PaperMarkup です。([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
}

重要な点:

  • PaperMarkup の初期化にはキャンバスサイズを定義する bounds が必要です。
  • subelementsMarkupOrderedSet 型の読み書き可能な順序付きコレクションです。
  • ShapeMarkup は図形要素で、初期化には位置 frame と設定 configuration が必要です。
  • subelements を変更した後は、markup.subelements に代入し直すことで変更が反映されます。

要素のインタラクションを制御する

デフォルトでは、キャンバス上の要素は自由に選択、ドラッグ、削除できます。ただし、テンプレート要素をロックしたい場面もあります。([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
}

重要な点:

  • allowedInteractionsMarkupInteractions のオプションセットです。
  • .readOnly は移動、サイズ変更、回転、削除、スタイル変更、選択をまとめて無効化する組み合わせフラグです。
  • 移動だけ許可して削除は禁止する、といった個別制御もできます。

スタイル変更

すべての要素には型ごとのプロパティがあります。ShapeMarkup には strokeColorfillColor があります。([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
}

重要な点:

  • subelements にはさまざまな Markup 準拠型が入るため、反復時には型変換が必要です。
  • updateOrAppend は既存要素を更新するか、新しい要素を追加します。
  • 変更後は paperMarkupViewController.markup に代入し直します。

装飾レイヤー(Adornments)

Adornments はキャンバス上に重ねる一時的なコントロールで、ドキュメントには保存されません。ボタン、注釈、共同作業 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
}

重要な点:

  • MarkupAdornmentUUID で一意に識別し、後続のタップ処理に使います。
  • anchor.canvas(location:) でキャンバス座標に固定され、ズームやスクロールに自動追従します。
  • imageConfiguration は SF Symbols をサポートします。
  • dragRegion: .fixed はボタン位置を固定し、scalesWithZoom: false はズームしても拡大しないことを表します。

装飾レイヤーのタップを処理する

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

重要な点:

  • デリゲートメソッドを実装して装飾レイヤーのタップイベントを受け取ります。
  • id を具体的な業務オブジェクト、たとえば漫画パネルのインデックスに対応付けます。
  • 必要に応じて Image Playground など別の画面を表示できます。

生成された画像を挿入する

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
}

重要な点:

  • ImageMarkup は画像要素を作成するために使い、位置と UIImage が必要です。
  • subelements に直接 append すると挿入できます。
  • markup を更新するとビューが再描画されます。

PencilKit との関係

PaperKit は PencilKit の上に構築されており、Apple Pencil の描画をサポートします。各ストロークは markup 要素になり、PencilKit のモデル API を利用できます。これらの API は、現在では文字認識とベジェ曲線パス変換にも対応しています。

重要な示唆

漫画制作 App:デモのような漫画エディタを作れます。allowedInteractions でパネルレイアウトをロックし、adornments で「画像を生成」ボタンを追加し、Image Playground と組み合わせてユーザーがすばやく制作できるようにします。入口:ShapeMarkup + MarkupAdornment

塗り絵 App:read-only の線画テンプレートを用意し、ユーザーは指定領域だけを塗れるようにします。subelements を反復して特定の図形を見つけ、fillColor を変更します。

共同注釈ツール:adornments を使って共同作業者のオンライン状態、カーソル位置、コメントマーカーを表示します。これらの情報はドキュメントには保存されず、リアルタイムに更新できます。入口:MarkupAdornment + 定期更新。

手書きノート検索:PencilKit の文字認識 API を使って手書き内容をテキストに変換し、TextMarkup 要素として挿入することで、ノートを検索可能にします。

テンプレート入力ツール:請求書、契約書、フォームなどの各種ドキュメントテンプレートを作成し、readOnly でテンプレート構造をロックします。ユーザーは空欄部分だけに入力できます。

関連セッション

コメント

GitHub Issues · utterances