pdfkit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

PDFKit

PDFKit

Display, navigate, search, annotate, and manipulate PDF documents with
PDFView
,
PDFDocument
,
PDFPage
,
PDFAnnotation
, and
PDFSelection
. Targets Swift 6.3 / iOS 26+.
借助
PDFView
PDFDocument
PDFPage
PDFAnnotation
PDFSelection
实现PDF文档的显示、导航、搜索、注释与处理。适配Swift 6.3 / iOS 26+版本。

Contents

目录

Setup

设置

PDFKit requires no entitlements or Info.plist entries.
swift
import PDFKit
Platform availability: iOS 11+, iPadOS 11+, Mac Catalyst 13.1+, macOS 10.4+, tvOS 11+, visionOS 1.0+.
PDFKit不需要额外的权限配置或Info.plist条目。
swift
import PDFKit
平台兼容性: iOS 11+、iPadOS 11+、Mac Catalyst 13.1+、macOS 10.4+、tvOS 11+、visionOS 1.0+。

Displaying PDFs

显示PDF

PDFView
is a
UIView
subclass that renders PDF content, handles zoom, scroll, text selection, and page navigation out of the box.
swift
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)
        }
    }
}
PDFView
UIView
的子类,可直接渲染PDF内容,原生支持缩放、滚动、文本选择和页面导航功能。
swift
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)
        }
    }
}

Display Modes

显示模式

ModeBehavior
.singlePage
One page at a time
.singlePageContinuous
Pages stacked vertically, scrollable
.twoUp
Two pages side by side
.twoUpContinuous
Two-up with continuous scrolling
模式表现
.singlePage
一次显示一页
.singlePageContinuous
页面垂直堆叠,支持滚动
.twoUp
两页并排显示
.twoUpContinuous
两页并排且支持连续滚动

Scaling and Appearance

缩放与外观设置

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

pdfView.displaysPageBreaks = true
pdfView.pageShadowsEnabled = true
pdfView.interpolationQuality = .high
swift
pdfView.autoScales = true
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.maxScaleFactor = 4.0

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

Loading Documents

加载文档

PDFDocument
loads from a URL,
Data
, or can be created empty.
swift
let fileDoc = PDFDocument(url: fileURL)
let dataDoc = PDFDocument(data: pdfData)
let emptyDoc = PDFDocument()
PDFDocument
可通过URL、
Data
加载,也可创建空文档。
swift
let fileDoc = PDFDocument(url: fileURL)
let dataDoc = PDFDocument(data: pdfData)
let emptyDoc = PDFDocument()

Password-Protected PDFs

密码保护的PDF

swift
guard let document = PDFDocument(url: url) else { return }
if document.isLocked {
    if !document.unlock(withPassword: userPassword) {
        // Show password prompt
    }
}
swift
guard let document = PDFDocument(url: url) else { return }
if document.isLocked {
    if !document.unlock(withPassword: userPassword) {
        // 显示密码输入提示
    }
}

Saving and Page Manipulation

保存与页面操作

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

// Pages (0-based)
let count = document.pageCount
document.insert(PDFPage(), at: count)
document.removePage(at: 2)
document.exchangePage(at: 0, withPageAt: 3)
swift
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)

Page Navigation

页面导航

PDFView
provides built-in navigation with history tracking.
swift
// Go to a specific page
if let page = pdfView.document?.page(at: 5) {
    pdfView.go(to: page)
}

// Sequential navigation
pdfView.goToNextPage(nil)
pdfView.goToPreviousPage(nil)
pdfView.goToFirstPage(nil)
pdfView.goToLastPage(nil)

// Check navigation state
if pdfView.canGoToNextPage { /* ... */ }

// History navigation
if pdfView.canGoBack { pdfView.goBack(nil) }

// Go to a specific point on a page
let destination = PDFDestination(page: page, at: CGPoint(x: 0, y: 500))
pdfView.go(to: destination)
PDFView
提供内置导航功能,支持历史记录追踪。
swift
// 跳转到指定页面
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)

Observing Page Changes

监听页面变化

swift
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 = "Page \(index + 1) of \(doc.pageCount)"
}
swift
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) 页"
}

Text Search and Selection

文本搜索与选择

Synchronous Search

同步搜索

swift
let results: [PDFSelection] = document.findString(
    "search term", withOptions: [.caseInsensitive]
)
swift
let results: [PDFSelection] = document.findString(
    "search term", withOptions: [.caseInsensitive]
)

Asynchronous Search

异步搜索

Use
PDFDocumentDelegate
for background searches on large documents. Implement
didMatchString(_:)
to receive each match and
documentDidEndDocumentFind(_:)
for completion.
对于大文档,可使用
PDFDocumentDelegate
进行后台搜索。实现
didMatchString(_:)
方法接收每个匹配结果,
documentDidEndDocumentFind(_:)
方法接收搜索完成通知。

Incremental Search and Find Interaction

增量搜索与查找交互

swift
// Find next match from current selection
let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive])

// System find bar (iOS 16+)
pdfView.isFindInteractionEnabled = true
swift
// 从当前选择内容开始查找下一个匹配项
let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive])

// 系统查找栏(iOS 16+)
pdfView.isFindInteractionEnabled = true

Text Extraction

文本提取

swift
let fullText = document.string                          // Entire document
let pageText = document.page(at: 0)?.string             // Single page
let attributed = document.page(at: 0)?.attributedString  // With formatting

// Region-based extraction
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
}
swift
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
}

Highlighting Search Results

高亮搜索结果

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

Annotations

注释

Annotations are created with
PDFAnnotation(bounds:forType:withProperties:)
and added to a
PDFPage
.
使用
PDFAnnotation(bounds:forType:withProperties:)
创建注释,再添加到
PDFPage
中。

Highlight Annotation

高亮注释

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

Text Note Annotation

文本笔记注释

swift
let note = PDFAnnotation(
    bounds: CGRect(x: 100, y: 700, width: 30, height: 30),
    forType: .text, withProperties: nil
)
note.contents = "This is a sticky note."
note.color = .systemYellow
note.iconType = .comment
page.addAnnotation(note)
swift
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)

Free Text Annotation

自由文本注释

swift
let freeText = PDFAnnotation(
    bounds: CGRect(x: 50, y: 600, width: 300, height: 40),
    forType: .freeText, withProperties: nil
)
freeText.contents = "Added commentary"
freeText.font = UIFont.systemFont(ofSize: 14)
freeText.fontColor = .darkGray
page.addAnnotation(freeText)
swift
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)

Link Annotation

链接注释

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

// Internal page link
link.destination = PDFDestination(page: targetPage, at: .zero)
swift
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)

Removing Annotations

删除注释

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

Annotation Subtypes Reference

注释子类型参考

SubtypeConstantPurpose
Highlight
.highlight
Text markup (yellow highlight)
Underline
.underline
Text markup (underline)
StrikeOut
.strikeOut
Text markup (strikethrough)
Text
.text
Sticky note icon
FreeText
.freeText
Inline text block
Ink
.ink
Freehand drawing paths
Link
.link
URL or page destination
Line
.line
Straight line with endpoints
Square
.square
Rectangle shape
Circle
.circle
Ellipse shape
Stamp
.stamp
Rubber stamp (Approved, etc.)
Widget
.widget
Form element (text field, checkbox)
子类型常量用途
Highlight
.highlight
文本标记(黄色高亮)
Underline
.underline
文本标记(下划线)
StrikeOut
.strikeOut
文本标记(删除线)
Text
.text
便签图标
FreeText
.freeText
内嵌文本块
Ink
.ink
手绘路径
Link
.link
URL或页面跳转目标
Line
.line
带端点的直线
Square
.square
矩形形状
Circle
.circle
椭圆形状
Stamp
.stamp
橡皮图章(如“已批准”等)
Widget
.widget
表单元素(文本框、复选框)

Thumbnails

缩略图

PDFThumbnailView

PDFThumbnailView

PDFThumbnailView
shows a strip of page thumbnails linked to a
PDFView
.
swift
let thumbnailView = PDFThumbnailView()
thumbnailView.pdfView = pdfView
thumbnailView.thumbnailSize = CGSize(width: 60, height: 80)
thumbnailView.layoutMode = .vertical
thumbnailView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(thumbnailView)
PDFThumbnailView
显示页面缩略图条,可与
PDFView
关联。
swift
let thumbnailView = PDFThumbnailView()
thumbnailView.pdfView = pdfView
thumbnailView.thumbnailSize = CGSize(width: 60, height: 80)
thumbnailView.layoutMode = .vertical
thumbnailView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(thumbnailView)

Generating Thumbnails Programmatically

程序化生成缩略图

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

// All pages
let thumbnails = (0..<document.pageCount).compactMap {
    document.page(at: $0)?.thumbnail(of: CGSize(width: 120, height: 160), for: .mediaBox)
}
swift
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 Integration

SwiftUI集成

Wrap
PDFView
in a
UIViewRepresentable
for SwiftUI.
swift
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
        }
    }
}
PDFView
封装在
UIViewRepresentable
中以适配SwiftUI。
swift
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
        }
    }
}

Usage

使用示例

swift
struct DocumentScreen: View {
    let url: URL

    var body: some View {
        if let document = PDFDocument(url: url) {
            PDFKitView(document: document)
                .ignoresSafeArea()
        } else {
            ContentUnavailableView("Unable to load PDF", systemImage: "doc.questionmark")
        }
    }
}
For interactive wrappers with page tracking, annotation hit detection, and coordinator patterns, see references/pdfkit-patterns.md.
swift
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

Page Overlays (iOS 16+)

页面叠加层(iOS 16+)

PDFPageOverlayViewProvider
places UIKit views on top of individual pages for interactive controls or custom rendering beyond standard annotations.
swift
class OverlayProvider: NSObject, PDFPageOverlayViewProvider {
    func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
        let overlay = UIView()
        // Add custom subviews
        return overlay
    }
}

pdfView.pageOverlayViewProvider = overlayProvider
PDFPageOverlayViewProvider
可在单个页面上方放置UIKit视图,用于添加交互控件或实现标准注释之外的自定义渲染。
swift
class OverlayProvider: NSObject, PDFPageOverlayViewProvider {
    func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
        let overlay = UIView()
        // 添加自定义子视图
        return overlay
    }
}

pdfView.pageOverlayViewProvider = overlayProvider

Common Mistakes

常见错误

DON'T: Force-unwrap PDFDocument init

错误做法:强制解包PDFDocument初始化方法

PDFDocument(url:)
and
PDFDocument(data:)
are failable initializers.
swift
// WRONG
let document = PDFDocument(url: url)!

// CORRECT
guard let document = PDFDocument(url: url) else { return }
PDFDocument(url:)
PDFDocument(data:)
是可失败初始化方法。
swift
// 错误写法
let document = PDFDocument(url: url)!

// 正确写法
guard let document = PDFDocument(url: url) else { return }

DON'T: Forget autoScales on PDFView

错误做法:忘记设置PDFView的autoScales属性

Without
autoScales
, the PDF renders at its native resolution.
swift
// WRONG
pdfView.document = document

// CORRECT
pdfView.autoScales = true
pdfView.document = document
不设置
autoScales
的话,PDF会以原生分辨率渲染。
swift
// 错误写法
pdfView.document = document

// 正确写法
pdfView.autoScales = true
pdfView.document = document

DON'T: Ignore PDF coordinate system in annotations

错误做法:忽略注释中的PDF坐标系

PDF page coordinates have origin at the bottom-left with Y increasing upward -- opposite of UIKit.
swift
// WRONG: UIKit coordinates
let bounds = CGRect(x: 50, y: 50, width: 200, height: 30)

// CORRECT: PDF coordinates (origin bottom-left)
let pageBounds = page.bounds(for: .mediaBox)
let pdfY = pageBounds.height - 50 - 30
let bounds = CGRect(x: 50, y: pdfY, width: 200, height: 30)
PDF页面坐标系原点在左下角,Y轴向上——与UIKit坐标系相反。
swift
// 错误写法:使用UIKit坐标系
let bounds = CGRect(x: 50, y: 50, width: 200, height: 30)

// 正确写法:使用PDF坐标系(原点左下角,Y轴向上)
let pageBounds = page.bounds(for: .mediaBox)
let pdfY = pageBounds.height - 50 - 30
let bounds = CGRect(x: 50, y: pdfY, width: 200, height: 30)

DON'T: Modify annotations on a background thread

错误做法:在后台线程修改注释

PDFKit classes are not thread-safe.
swift
// WRONG
DispatchQueue.global().async { page.addAnnotation(annotation) }

// CORRECT
DispatchQueue.main.async { page.addAnnotation(annotation) }
PDFKit类不是线程安全的。
swift
// 错误写法
DispatchQueue.global().async { page.addAnnotation(annotation) }

// 正确写法
DispatchQueue.main.async { page.addAnnotation(annotation) }

DON'T: Compare PDFDocument with == in UIViewRepresentable

错误做法:在UIViewRepresentable中用==比较PDFDocument

PDFDocument
is a reference type. Use identity (
!==
).
swift
// WRONG: Always replaces document
func updateUIView(_ pdfView: PDFView, context: Context) {
    pdfView.document = document
}

// CORRECT
func updateUIView(_ pdfView: PDFView, context: Context) {
    if pdfView.document !== document {
        pdfView.document = document
    }
}
PDFDocument
是引用类型,应使用身份比较(
!==
)。
swift
// 错误写法:总是替换文档
func updateUIView(_ pdfView: PDFView, context: Context) {
    pdfView.document = document
}

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

Review Checklist

检查清单

  • PDFDocument
    init uses optional binding, not force-unwrap
  • pdfView.autoScales = true
    set for proper initial display
  • Page indices checked against
    pageCount
    before access
  • displayMode
    and
    displayDirection
    configured to match design
  • Annotations use PDF coordinate space (origin bottom-left, Y up)
  • All PDFKit mutations happen on the main thread
  • Password-protected PDFs handled with
    isLocked
    /
    unlock(withPassword:)
  • SwiftUI wrapper uses
    !==
    identity check in
    updateUIView
  • PDFViewPageChanged
    notification observed for page tracking
  • PDFThumbnailView.pdfView
    linked to the main
    PDFView
  • Large-document search uses async
    beginFindString
    with delegate
  • Saved documents use
    write(to:withOptions:)
    when encryption needed
  • PDFDocument
    初始化使用可选绑定,而非强制解包
  • 设置
    pdfView.autoScales = true
    以保证初始显示效果
  • 访问页面前先检查索引是否在
    pageCount
    范围内
  • 根据设计需求配置
    displayMode
    displayDirection
  • 注释使用PDF坐标空间(原点左下角,Y轴向上)
  • 所有PDFKit修改操作都在主线程执行
  • 通过
    isLocked
    /
    unlock(withPassword:)
    处理密码保护的PDF
  • SwiftUI封装在
    updateUIView
    中使用
    !==
    身份比较
  • 监听
    PDFViewPageChanged
    通知以追踪页面变化
  • PDFThumbnailView.pdfView
    与主
    PDFView
    关联
  • 大文档搜索使用异步
    beginFindString
    并结合代理
  • 需要加密时使用
    write(to:withOptions:)
    保存文档

References

参考资料