Communitygithub.com

JordanCoin/ios-skills-collection

使用 PDFKit 顯示與操作 PDF 文件。適用於嵌入 PDFView 顯示 PDF 檔案、建立或修改 PDFDocument 實例、加入註解(螢光標示、筆記、簽名)、使用 PDFSelection 擷取文字、導覽頁面、產生縮圖、填寫 PDF 表單,或將 PDFView 包裝於 SwiftUI 中。

ios-skills-collection 是什麼?

這是一個專注於 Swift 6.3 / iOS 26+ 環境下 PDFKit 框架的完整指南技能。它提供從 PDF 顯示、文件載入(含密碼保護)、頁面導覽、文字搜尋選取、註解操作(螢光標示、筆記、連結等)、縮圖生成到 SwiftUI 整合的所有關鍵功能。該技能特別強調了常見錯誤的避免,例如在背景線程修改註解、忘記設定 `autoScales` 或錯誤比較 `PDFDocument` 實例,並提供詳細的審查清單幫助開發者確保程式碼品質。此技能支援 iOS、iPadOS、macOS、tvOS 和 visionOS 等平台,並可與 Claude Code、Cursor 等 AI 代理工具搭配使用,讓開發者能高效地在應用程式中整合 PDF 處理功能。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/JordanCoin/ios-skills-collection/tree/HEAD/skills/dpearson2699--pdfkit

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

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一次顯示一頁
.singlePageContinuous頁面垂直堆疊,可捲動
.twoUp兩頁並排顯示
.twoUpContinuous兩頁並排,連續捲動

縮放與外觀

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

guard let document = PDFDocument(url: url) else { return }
if document.isLocked {
    if !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.highlight文字標示 (黃色螢光筆)
Underline.underline文字標示 (底線)
StrikeOut.strikeOut文字標示 (刪除線)
Text.text便利貼圖示
FreeText.freeText內嵌文字區塊
Ink.ink手繪路徑
Link.linkURL 或頁面目的地
Line.line具有端點的直線
Square.square矩形形狀
Circle.circle橢圓形狀
Stamp.stamp橡皮圖章 (已核准等)
Widget.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 整合

將 PDFView 包裝在 UIViewRepresentable 中以供 SwiftUI 使用。

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

// 正確
guard let document = PDFDocument(url: url) else { return }

不要:忘記在 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 變更都在主執行緒上進行
  • 已使用 isLocked / unlock(withPassword:) 處理受密碼保護的 PDF
  • 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.

相關技能