Communitygithub.com

JordanCoin/ios-skills-collection

PDFKit を使用して PDF 文書の表示と操作を行います。PDFView を使った PDF ファイルの埋め込み、PDFDocument インスタンスの作成や変更、注釈(ハイライト、ノート、署名)の追加、PDFSelection によるテキスト抽出、ページナビゲーション、サムネイル生成、PDF フォームの入力、または SwiftUI での PDFView のラッピングに使用します。

ios-skills-collection とは?

このスキルは、Apple の PDFKit フレームワークを活用して、iOS アプリ内で PDF 文書を完全に制御するための包括的なガイドを提供します。PDFView を使用した表示、PDFDocument による文書の読み込み・保存・ページ操作、PDFAnnotation を用いたハイライトやノートなどの注釈追加、PDFSelection によるテキスト検索と抽出、PDFThumbnailView を使ったサムネイル表示、さらには SwiftUI への統合方法までを網羅しています。iOS 11+、iPadOS 11+、macOS 10.4+ など複数のプラットフォームで動作し、パスワード保護された PDF の処理や、非同期検索、ページオーバーレイなどの高度な機能もサポートしています。よくある間違いとして、PDFDocument の強制アンラップや、PDF 座標系の誤った使用、バックグラウンドスレッドでの注釈変更などが挙げられており、それらを避けるためのベストプラクティスも詳しく説明されています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/JordanCoin/ios-skills-collection/tree/HEAD/skills/dpearson2699--pdfkit

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

PDFKit

PDFView、PDFDocument、PDFPage、PDFAnnotation、PDFSelection を使用して PDF 文書の表示、ナビゲーション、検索、注釈、操作を行います。対象は Swift 6.3 / iOS 26+ です。

目次

セットアップ

PDFKit はエンタイトルメントや Info.plist のエントリを必要としません。

import PDFKit

プラットフォームの利用可能性: iOS 11+、iPadOS 11+、Mac Catalyst 13.1+、macOS 10.4+、tvOS 11+、visionOS 1.0+。

PDF の表示

PDFView は UIView のサブクラスで、PDF コンテンツのレンダリング、ズーム、スクロール、テキスト選択、ページナビゲーションをそのままサポートします。

import PDFKit
import UIKit

class PDFViewController: UIViewController {
    let pdfView = PDFView()

    override func viewDidLoad() {
        super.viewDidLoad()
        pdfView.frame = view.bounds
        pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(pdfView)

        pdfView.autoScales = true
        pdfView.displayMode = .singlePageContinuous
        pdfView.displayDirection = .vertical

        if let url = Bundle.main.url(forResource: "sample", withExtension: "pdf") {
            pdfView.document = PDFDocument(url: url)
        }
    }
}

表示モード

モード動作
.singlePage一度に1ページ
.singlePageContinuousページを縦に積み重ね、スクロール可能
.twoUp2ページを横に並べて表示
.twoUpContinuous2ページ表示で連続スクロール

拡大縮小と外観

pdfView.autoScales = true
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.maxScaleFactor = 4.0

pdfView.displaysPageBreaks = true
pdfView.pageShadowsEnabled = true
pdfView.interpolationQuality = .high

文書の読み込み

PDFDocument は URL、Data、または空の状態から作成できます。

let fileDoc = PDFDocument(url: fileURL)
let dataDoc = PDFDocument(data: pdfData)
let emptyDoc = PDFDocument()

パスワードで保護された PDF

// パスワードで保護された PDF の処理
if let document = PDFDocument(url: url), document.isLocked {
    document.unlock(withPassword: userPassword)
}

保存とページ操作

document.write(to: outputURL)
document.write(to: outputURL, withOptions: [
    .ownerPasswordOption: "ownerPass", .userPasswordOption: "userPass"
])
let data = document.dataRepresentation()

// ページ(0 ベース)
let count = document.pageCount
document.insert(PDFPage(), at: count)
document.removePage(at: 2)
document.exchangePage(at: 0, withPageAt: 3)

ページナビゲーション

PDFView は履歴追跡機能を備えた組み込みのナビゲーションを提供します。

// 特定のページに移動
if let page = pdfView.document?.page(at: 5) {
    pdfView.go(to: page)
}

// 順次ナビゲーション
pdfView.goToNextPage(nil)
pdfView.goToPreviousPage(nil)
pdfView.goToFirstPage(nil)
pdfView.goToLastPage(nil)

// ナビゲーション状態の確認
if pdfView.canGoToNextPage { /* ... */ }

// 履歴ナビゲーション
if pdfView.canGoBack { pdfView.goBack(nil) }

// ページ上の特定の位置に移動
let destination = PDFDestination(page: page, at: CGPoint(x: 0, y: 500))
pdfView.go(to: destination)

ページ変更の監視

NotificationCenter.default.addObserver(
    self, selector: #selector(pageChanged),
    name: .PDFViewPageChanged, object: pdfView
)

@objc func pageChanged(_ notification: Notification) {
    guard let page = pdfView.currentPage,
          let doc = pdfView.document else { return }
    let index = doc.index(for: page)
    pageLabel.text = "ページ \(index + 1) / \(doc.pageCount)"
}

テキスト検索と選択

同期的検索

let results: [PDFSelection] = document.findString(
    "検索語", withOptions: [.caseInsensitive]
)

非同期検索

大きな文書でのバックグラウンド検索には PDFDocumentDelegate を使用します。各一致結果を受け取るには didMatchString(_:) を、検索完了時には documentDidEndDocumentFind(_:) を実装します。

インクリメンタル検索と検索インタラクション

// 現在の選択範囲から次の一致を検索
let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive])

// システムの検索バー(iOS 16+)
pdfView.isFindInteractionEnabled = true

テキスト抽出

let fullText = document.string                         // 文書全体
let pageText = document.page(at: 0)?.string             // 単一ページ
let attributed = document.page(at: 0)?.attributedString  // 書式付き

// 領域ベースの抽出
if let page = document.page(at: 0) {
    let selection = page.selection(for: CGRect(x: 50, y: 50, width: 400, height: 200))
    let text = selection?.string
}

検索結果のハイライト表示

let results = document.findString("important", withOptions: [.caseInsensitive])
for selection in results { selection.color = .yellow }
pdfView.highlightedSelections = results

if let first = results.first {
    pdfView.setCurrentSelection(first, animate: true)
    pdfView.go(to: first)
}

注釈

注釈は PDFAnnotation(bounds:forType:withProperties:) で作成し、PDFPage に追加します。

ハイライト注釈

func addHighlight(to page: PDFPage, selection: PDFSelection) {
    let highlight = PDFAnnotation(
        bounds: selection.bounds(for: page),
        forType: .highlight, withProperties: nil
    )
    highlight.color = UIColor.yellow.withAlphaComponent(0.5)
    page.addAnnotation(highlight)
}

テキストノート注釈

let note = PDFAnnotation(
    bounds: CGRect(x: 100, y: 700, width: 30, height: 30),
    forType: .text, withProperties: nil
)
note.contents = "これは付箋です。"
note.color = .systemYellow
note.iconType = .comment
page.addAnnotation(note)

自由テキスト注釈

let freeText = PDFAnnotation(
    bounds: CGRect(x: 50, y: 600, width: 300, height: 40),
    forType: .freeText, withProperties: nil
)
freeText.contents = "追加されたコメント"
freeText.font = UIFont.systemFont(ofSize: 14)
freeText.fontColor = .darkGray
page.addAnnotation(freeText)

リンク注釈

let link = PDFAnnotation(
    bounds: CGRect(x: 50, y: 500, width: 200, height: 20),
    forType: .link, withProperties: nil
)
link.url = URL(string: "https://example.com")
page.addAnnotation(link)

// 内部ページリンク
link.destination = PDFDestination(page: targetPage, at: .zero)

注釈の削除

for annotation in page.annotations {
    page.removeAnnotation(annotation)
}

注釈サブタイプリファレンス

サブタイプ定数目的
ハイライト.highlightテキストマークアップ(黄色ハイライト)
下線.underlineテキストマークアップ(下線)
取り消し線.strikeOutテキストマークアップ(取り消し線)
テキスト.text付箋アイコン
自由テキスト.freeTextインラインテキストブロック
インク.inkフリーハンド描画パス
リンク.linkURL またはページ移動先
線.line端点を持つ直線
正方形.square長方形シェイプ
円.circle楕円シェイプ
スタンプ.stampゴム印(承認済みなど)
ウィジェット.widgetフォーム要素(テキストフィールド、チェックボックス)

サムネイル

PDFThumbnailView

PDFThumbnailView は PDFView にリンクされたページサムネイルのストリップを表示します。

let thumbnailView = PDFThumbnailView()
thumbnailView.pdfView = pdfView
thumbnailView.thumbnailSize = CGSize(width: 60, height: 80)
thumbnailView.layoutMode = .vertical
thumbnailView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(thumbnailView)

プログラムによるサムネイル生成

let thumbnail = page.thumbnail(of: CGSize(width: 120, height: 160), for: .mediaBox)

// 全ページ
let thumbnails = (0..<document.pageCount).compactMap {
    document.page(at: $0)?.thumbnail(of: CGSize(width: 120, height: 160), for: .mediaBox)
}

SwiftUI 統合

SwiftUI で使用するには PDFView を UIViewRepresentable でラップします。

import SwiftUI
import PDFKit

struct PDFKitView: UIViewRepresentable {
    let document: PDFDocument

    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        pdfView.autoScales = true
        pdfView.displayMode = .singlePageContinuous
        pdfView.document = document
        return pdfView
    }

    func updateUIView(_ pdfView: PDFView, context: Context) {
        if pdfView.document !== document {
            pdfView.document = document
        }
    }
}

使用例

struct DocumentScreen: View {
    let url: URL

    var body: some View {
        if let document = PDFDocument(url: url) {
            PDFKitView(document: document)
                .ignoresSafeArea()
        } else {
            ContentUnavailableView("PDF を読み込めません", systemImage: "doc.questionmark")
        }
    }
}

ページ追跡、注釈ヒット検出、コーディネーターパターンを備えたインタラクティブラッパーについては、references/pdfkit-patterns.md を参照してください。

ページオーバーレイ(iOS 16+)

PDFPageOverlayViewProvider は、標準の注釈を超えたインタラクティブなコントロールやカスタムレンダリングのために、個々のページの上に UIKit ビューを配置します。

class OverlayProvider: NSObject, PDFPageOverlayViewProvider {
    func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
        let overlay = UIView()
        // カスタムサブビューを追加
        return overlay
    }
}

pdfView.pageOverlayViewProvider = overlayProvider

よくある間違い

間違い: PDFDocument イニシャライザを強制アンラップする

PDFDocument(url:) と PDFDocument(data:) は失敗可能イニシャライザです。

// 間違い
let document = PDFDocument(url: url)!

// 正しい
if let document = PDFDocument(url: url) {
    // 文書を使用
}

間違い: PDFView の autoScales を忘れる

autoScales がないと、PDF はネイティブ解像度でレンダリングされます。

// 間違い
pdfView.document = document

// 正しい
pdfView.autoScales = true
pdfView.document = document

間違い: 注釈で PDF 座標系を無視する

PDF ページ座標は左下が原点で Y が上方向に増加します。これは UIKit とは逆です。

// 間違い: UIKit 座標
let bounds = CGRect(x: 50, y: 50, width: 200, height: 30)

// 正しい: PDF 座標(左下原点)
let pageBounds = page.bounds(for: .mediaBox)
let pdfY = pageBounds.height - 50 - 30
let bounds = CGRect(x: 50, y: pdfY, width: 200, height: 30)

間違い: バックグラウンドスレッドで注釈を変更する

PDFKit クラスはスレッドセーフではありません。

// 間違い
DispatchQueue.global().async { page.addAnnotation(annotation) }

// 正しい
DispatchQueue.main.async { page.addAnnotation(annotation) }

間違い: UIViewRepresentable で PDFDocument を == で比較する

PDFDocument は参照型です。同一性(!==)を使用してください。

// 間違い: 常に文書を置き換える
func updateUIView(_ pdfView: PDFView, context: Context) {
    pdfView.document = document
}

// 正しい
func updateUIView(_ pdfView: PDFView, context: Context) {
    if pdfView.document !== document {
        pdfView.document = document
    }
}

レビューチェックリスト

  • PDFDocument の初期化はオプショナルバインディングを使用し、強制アンラップしない
  • pdfView.autoScales = true が適切な初期表示のために設定されている
  • ページインデックスはアクセス前に pageCount と照合している
  • displayMode と displayDirection がデザインに合わせて設定されている
  • 注釈は PDF 座標空間(左下原点、Y は上向き)を使用している
  • すべての PDFKit の変更はメインスレッドで行われている
  • パスワード保護された PDF は isLocked / unlock(withPassword:) で処理している
  • SwiftUI ラッパーは updateUIView で !== による同一性チェックを使用している
  • PDFViewPageChanged 通知をページ追跡のために監視している
  • PDFThumbnailView.pdfView がメインの PDFView にリンクされている
  • 大きな文書の検索には、デリゲートを使った非同期 beginFindString を使用している
  • 保存された文書は、暗号化が必要な場合に write(to:withOptions:) を使用している

参照

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

JordanCoin/ios-skills-collection

Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries. Use when the user asks about GitHub issues, pull requests, workflows, or wants to interact with GitHub repositories from the command line — including tasks like check CI status, create PR, list issues, or query the GitHub API.

JordanCoin/ios-skills-collection

Create media playback experiences using AVKit. Use when adding video players with AVPlayerViewController, enabling Picture-in-Picture, routing media with AirPlay, using SwiftUI VideoPlayer views, configuring transport controls, displaying subtitles and closed captions, or integrating AVFoundation playback with system UI.

JordanCoin/ios-skills-collection

Integrate and optimize Core ML models in iOS apps for on-device machine learning inference. Covers model loading (.mlmodelc, .mlpackage), predictions with auto-generated classes and MLFeatureProvider, compute unit configuration (CPU, GPU, Neural Engine), MLTensor, VNCoreMLRequest, MLComputePlan, multi-model pipelines, and deployment strategies. Use when loading Core ML models, making predictions, configuring compute units, or profiling model performance.

JordanCoin/ios-skills-collection

Implement, review, or improve maps and location features in iOS/macOS apps using MapKit and CoreLocation. Use when working with Map views, annotations, markers, polylines, user location tracking, geocoding, reverse geocoding, search/autocomplete, directions and routes, geofencing, region monitoring, CLLocationUpdate async streams, or location authorization flows. Also use when working with maps, coordinates, addresses, places, directions, distance calculations, or location-based features in Swift apps.

JordanCoin/ios-skills-collection

Implement, review, or improve in-app tips and onboarding using Apple's TipKit framework. Use when adding feature discovery tooltips, onboarding flows, contextual tips, first-run experiences, coach marks, or working with Tip protocol, TipView, popoverTip, tip rules, tip events, or feature education UI.

JordanCoin/ios-skills-collection

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-collection

When the user wants to implement, optimize, or use App Clips for app discovery and conversion. Use when the user mentions "App Clip", "app clip code", "mini app", "instant app", "App Clip card", "App Clip link", "no download required", "instant experience", or wants to understand how App Clips appear in App Store search. For general App Store discoverability, see aso-audit. For marketing campaigns, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to plan a launch strategy for a new app or major update. Also use when the user mentions "app launch", "launch plan", "launch checklist", "pre-launch", "launch day", or "how to launch my app". For ongoing ASO after launch, see aso-audit. For paid acquisition during launch, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to analyze their own app's actual performance data from App Store Connect — real downloads, revenue, IAP, subscriptions, trials, or country breakdowns synced via Appeeky Connect. Use when the user asks about "my downloads", "my revenue", "how is my app performing", "ASC data", "sales and trends", "my subscription numbers", "App Store Connect metrics", or wants to compare periods or top markets. For third-party app estimates, see app-analytics. For subscription analytics depth, see monetization-strategy.

JordanCoin/ios-skills-collection

When the user wants a full ASO health audit, review their App Store listing quality, or diagnose why their app isn't ranking. Also use when the user mentions "ASO audit", "ASO score", "why am I not ranking", "listing review", or "optimize my app store page". For keyword-specific research, see keyword-research. For metadata writing, see metadata-optimization.

JordanCoin/ios-skills-collection

Always use when working on any iOS, macOS, Swift, SwiftUI, Xcode, or Apple platform project. Routes to the correct specialized skills based on development phase and task context. This collection contains 210 skills from 28 authors covering the full iOS lifecycle.

関連スキル