pdfkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePDFKit
PDFKit
Display, navigate, search, annotate, and manipulate PDF documents with
, , , , and .
Targets Swift 6.3 / iOS 26+.
PDFViewPDFDocumentPDFPagePDFAnnotationPDFSelection借助、、、和实现PDF文档的显示、导航、搜索、注释与处理。适配Swift 6.3 / iOS 26+版本。
PDFViewPDFDocumentPDFPagePDFAnnotationPDFSelectionContents
目录
Setup
设置
PDFKit requires no entitlements or Info.plist entries.
swift
import PDFKitPlatform 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
PDFViewUIViewswift
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)
}
}
}PDFViewUIViewswift
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
显示模式
| Mode | Behavior |
|---|---|
| One page at a time |
| Pages stacked vertically, scrollable |
| Two pages side by side |
| Two-up with continuous scrolling |
| 模式 | 表现 |
|---|---|
| 一次显示一页 |
| 页面垂直堆叠,支持滚动 |
| 两页并排显示 |
| 两页并排且支持连续滚动 |
Scaling and Appearance
缩放与外观设置
swift
pdfView.autoScales = true
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.maxScaleFactor = 4.0
pdfView.displaysPageBreaks = true
pdfView.pageShadowsEnabled = true
pdfView.interpolationQuality = .highswift
pdfView.autoScales = true
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.maxScaleFactor = 4.0
pdfView.displaysPageBreaks = true
pdfView.pageShadowsEnabled = true
pdfView.interpolationQuality = .highLoading Documents
加载文档
PDFDocumentDataswift
let fileDoc = PDFDocument(url: fileURL)
let dataDoc = PDFDocument(data: pdfData)
let emptyDoc = PDFDocument()PDFDocumentDataswift
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
页面导航
PDFViewswift
// 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)PDFViewswift
// 跳转到指定页面
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 for background searches on large documents.
Implement to receive each match and
for completion.
PDFDocumentDelegatedidMatchString(_:)documentDidEndDocumentFind(_:)对于大文档,可使用进行后台搜索。实现方法接收每个匹配结果,方法接收搜索完成通知。
PDFDocumentDelegatedidMatchString(_:)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 = trueswift
// 从当前选择内容开始查找下一个匹配项
let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive])
// 系统查找栏(iOS 16+)
pdfView.isFindInteractionEnabled = trueText 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
and added to a .
PDFAnnotation(bounds:forType:withProperties:)PDFPage使用创建注释,再添加到中。
PDFAnnotation(bounds:forType:withProperties:)PDFPageHighlight 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
注释子类型参考
| Subtype | Constant | Purpose |
|---|---|---|
| Highlight | | Text markup (yellow highlight) |
| Underline | | Text markup (underline) |
| StrikeOut | | Text markup (strikethrough) |
| Text | | Sticky note icon |
| FreeText | | Inline text block |
| Ink | | Freehand drawing paths |
| Link | | URL or page destination |
| Line | | Straight line with endpoints |
| Square | | Rectangle shape |
| Circle | | Ellipse shape |
| Stamp | | Rubber stamp (Approved, etc.) |
| Widget | | Form element (text field, checkbox) |
| 子类型 | 常量 | 用途 |
|---|---|---|
| Highlight | | 文本标记(黄色高亮) |
| Underline | | 文本标记(下划线) |
| StrikeOut | | 文本标记(删除线) |
| Text | | 便签图标 |
| FreeText | | 内嵌文本块 |
| Ink | | 手绘路径 |
| Link | | URL或页面跳转目标 |
| Line | | 带端点的直线 |
| Square | | 矩形形状 |
| Circle | | 椭圆形状 |
| Stamp | | 橡皮图章(如“已批准”等) |
| Widget | | 表单元素(文本框、复选框) |
Thumbnails
缩略图
PDFThumbnailView
PDFThumbnailView
PDFThumbnailViewPDFViewswift
let thumbnailView = PDFThumbnailView()
thumbnailView.pdfView = pdfView
thumbnailView.thumbnailSize = CGSize(width: 60, height: 80)
thumbnailView.layoutMode = .vertical
thumbnailView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(thumbnailView)PDFThumbnailViewPDFViewswift
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 in a for SwiftUI.
PDFViewUIViewRepresentableswift
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
}
}
}将封装在中以适配SwiftUI。
PDFViewUIViewRepresentableswift
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+)
PDFPageOverlayViewProviderswift
class OverlayProvider: NSObject, PDFPageOverlayViewProvider {
func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
let overlay = UIView()
// Add custom subviews
return overlay
}
}
pdfView.pageOverlayViewProvider = overlayProviderPDFPageOverlayViewProviderswift
class OverlayProvider: NSObject, PDFPageOverlayViewProvider {
func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
let overlay = UIView()
// 添加自定义子视图
return overlay
}
}
pdfView.pageOverlayViewProvider = overlayProviderCommon Mistakes
常见错误
DON'T: Force-unwrap PDFDocument init
错误做法:强制解包PDFDocument初始化方法
PDFDocument(url:)PDFDocument(data:)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 , the PDF renders at its native resolution.
autoScalesswift
// WRONG
pdfView.document = document
// CORRECT
pdfView.autoScales = true
pdfView.document = document不设置的话,PDF会以原生分辨率渲染。
autoScalesswift
// 错误写法
pdfView.document = document
// 正确写法
pdfView.autoScales = true
pdfView.document = documentDON'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!==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
检查清单
- init uses optional binding, not force-unwrap
PDFDocument - set for proper initial display
pdfView.autoScales = true - Page indices checked against before access
pageCount - and
displayModeconfigured to match designdisplayDirection - Annotations use PDF coordinate space (origin bottom-left, Y up)
- All PDFKit mutations happen on the main thread
- Password-protected PDFs handled with /
isLockedunlock(withPassword:) - SwiftUI wrapper uses identity check in
!==updateUIView - notification observed for page tracking
PDFViewPageChanged - linked to the main
PDFThumbnailView.pdfViewPDFView - Large-document search uses async with delegate
beginFindString - Saved documents use when encryption needed
write(to:withOptions:)
- 初始化使用可选绑定,而非强制解包
PDFDocument - 设置以保证初始显示效果
pdfView.autoScales = true - 访问页面前先检查索引是否在范围内
pageCount - 根据设计需求配置和
displayModedisplayDirection - 注释使用PDF坐标空间(原点左下角,Y轴向上)
- 所有PDFKit修改操作都在主线程执行
- 通过/
isLocked处理密码保护的PDFunlock(withPassword:) - SwiftUI封装在中使用
updateUIView身份比较!== - 监听通知以追踪页面变化
PDFViewPageChanged - 将与主
PDFThumbnailView.pdfView关联PDFView - 大文档搜索使用异步并结合代理
beginFindString - 需要加密时使用保存文档
write(to:withOptions:)
References
参考资料
- Extended patterns (forms, watermarks, merging, printing, overlays, outlines, custom drawing): references/pdfkit-patterns.md
- PDFKit framework
- PDFView
- PDFDocument
- PDFPage
- PDFAnnotation
- PDFSelection
- PDFThumbnailView
- PDFPageOverlayViewProvider
- Adding Widgets to a PDF Document
- Adding Custom Graphics to a PDF
- 扩展模式(表单、水印、合并、打印、叠加层、大纲、自定义绘制):references/pdfkit-patterns.md
- PDFKit框架
- PDFView
- PDFDocument
- PDFPage
- PDFAnnotation
- PDFSelection
- PDFThumbnailView
- PDFPageOverlayViewProvider
- 向PDF文档添加Widget
- 向PDF添加自定义图形