$npx -y skills add dpearson2699/swift-ios-skills --skill pdfkitDisplay and manipulate PDF documents using PDFKit. Use when embedding PDFView to show PDF files, creating or modifying PDFDocument instances, adding annotations (highlights, notes, signature widgets), extracting text with PDFSelection, navigating pages, generating thumbnails, fil
| 1 | # PDFKit |
| 2 | |
| 3 | Display, navigate, search, annotate, and manipulate PDF documents with `PDFView`, `PDFDocument`, `PDFPage`, `PDFAnnotation`, and `PDFSelection`. |
| 4 | |
| 5 | ## Contents |
| 6 | |
| 7 | - [Setup](#setup) |
| 8 | - [Displaying PDFs](#displaying-pdfs) |
| 9 | - [Loading Documents](#loading-documents) |
| 10 | - [Page Navigation](#page-navigation) |
| 11 | - [Text Search and Selection](#text-search-and-selection) |
| 12 | - [Annotations](#annotations) |
| 13 | - [Thumbnails](#thumbnails) |
| 14 | - [SwiftUI Integration](#swiftui-integration) |
| 15 | - [Common Mistakes](#common-mistakes) |
| 16 | - [Review Checklist](#review-checklist) |
| 17 | - [References](#references) |
| 18 | |
| 19 | ## Setup |
| 20 | |
| 21 | PDFKit requires no entitlements or Info.plist entries. |
| 22 | |
| 23 | ```swift |
| 24 | import PDFKit |
| 25 | ``` |
| 26 | |
| 27 | | API | Availability | |
| 28 | |---|---| |
| 29 | | PDFKit framework | iOS/iPadOS/tvOS 11+, Mac Catalyst 13.1+, macOS 10.4+, visionOS 1.0+ | |
| 30 | | Find interaction and page overlays | iOS/iPadOS 16+ | |
| 31 | |
| 32 | ## Displaying PDFs |
| 33 | |
| 34 | `PDFView` renders PDF content and handles zoom, scrolling, text selection, and page navigation. |
| 35 | |
| 36 | ```swift |
| 37 | import PDFKit |
| 38 | import UIKit |
| 39 | |
| 40 | class PDFViewController: UIViewController { |
| 41 | let pdfView = PDFView() |
| 42 | |
| 43 | override func viewDidLoad() { |
| 44 | super.viewDidLoad() |
| 45 | pdfView.frame = view.bounds |
| 46 | pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight] |
| 47 | view.addSubview(pdfView) |
| 48 | |
| 49 | pdfView.autoScales = true |
| 50 | pdfView.displayMode = .singlePageContinuous |
| 51 | pdfView.displayDirection = .vertical |
| 52 | |
| 53 | if let url = Bundle.main.url(forResource: "sample", withExtension: "pdf") { |
| 54 | pdfView.document = PDFDocument(url: url) |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | ``` |
| 59 | |
| 60 | ### Display Modes |
| 61 | |
| 62 | | Mode | Behavior | |
| 63 | |---|---| |
| 64 | | `.singlePage` | One page at a time | |
| 65 | | `.singlePageContinuous` | Pages stacked vertically, scrollable | |
| 66 | | `.twoUp` | Two pages side by side | |
| 67 | | `.twoUpContinuous` | Two-up with continuous scrolling | |
| 68 | |
| 69 | ### Scaling and Appearance |
| 70 | |
| 71 | ```swift |
| 72 | pdfView.autoScales = true |
| 73 | pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit |
| 74 | pdfView.maxScaleFactor = 4.0 |
| 75 | |
| 76 | pdfView.displaysPageBreaks = true |
| 77 | pdfView.pageShadowsEnabled = true |
| 78 | pdfView.interpolationQuality = .high |
| 79 | ``` |
| 80 | |
| 81 | ## Loading Documents |
| 82 | |
| 83 | `PDFDocument` loads from a URL, `Data`, or can be created empty. |
| 84 | |
| 85 | ```swift |
| 86 | let fileDoc = PDFDocument(url: fileURL) |
| 87 | let dataDoc = PDFDocument(data: pdfData) |
| 88 | let emptyDoc = PDFDocument() |
| 89 | ``` |
| 90 | |
| 91 | ### Password-Protected PDFs |
| 92 | |
| 93 | ```swift |
| 94 | guard let document = PDFDocument(url: url) else { return } |
| 95 | if document.isLocked { |
| 96 | if !document.unlock(withPassword: userPassword) { |
| 97 | // Show password prompt |
| 98 | } |
| 99 | } |
| 100 | ``` |
| 101 | |
| 102 | ### Saving and Page Manipulation |
| 103 | |
| 104 | ```swift |
| 105 | document.write(to: outputURL) |
| 106 | document.write(to: outputURL, withOptions: [ |
| 107 | .ownerPasswordOption: "ownerPass", .userPasswordOption: "userPass" |
| 108 | ]) |
| 109 | let data = document.dataRepresentation() |
| 110 | |
| 111 | // Pages are zero-based. Validate indices; out-of-range calls raise exceptions. |
| 112 | let count = document.pageCount |
| 113 | document.insert(PDFPage(), at: count) |
| 114 | if document.pageCount > 2 { |
| 115 | document.removePage(at: 2) |
| 116 | } |
| 117 | if document.pageCount > 3 { |
| 118 | document.exchangePage(at: 0, withPageAt: 3) |
| 119 | } |
| 120 | ``` |
| 121 | |
| 122 | ## Page Navigation |
| 123 | |
| 124 | `PDFView` provides built-in navigation with history tracking. |
| 125 | |
| 126 | ```swift |
| 127 | // Go to a specific page |
| 128 | let pageIndex = 5 |
| 129 | if let document = pdfView.document, |
| 130 | pageIndex >= 0, |
| 131 | pageIndex < document.pageCount, |
| 132 | let page = document.page(at: pageIndex) { |
| 133 | pdfView.go(to: page) |
| 134 | } |
| 135 | |
| 136 | // Sequential navigation |
| 137 | pdfView.goToNextPage(nil) |
| 138 | pdfView.goToPreviousPage(nil) |
| 139 | pdfView.goToFirstPage(nil) |
| 140 | pdfView.goToLastPage(nil) |
| 141 | |
| 142 | // Check navigation state |
| 143 | if pdfView.canGoToNextPage { /* ... */ } |
| 144 | |
| 145 | // History navigation |
| 146 | if pdfView.canGoBack { pdfView.goBack(nil) } |
| 147 | |
| 148 | // Go to a specific point on the current page |
| 149 | if let page = pdfView.currentPage { |
| 150 | let destination = PDFDestination(page: page, at: CGPoint(x: 0, y: 500)) |
| 151 | pdfView.go(to: destination) |
| 152 | } |
| 153 | ``` |
| 154 | |
| 155 | ### Observing Page Changes |
| 156 | |
| 157 | ```swift |
| 158 | NotificationCenter.default.addObserver( |
| 159 | self, selector: #selector(pageChanged), |
| 160 | name: .PDFViewPageChanged, object: pdfView |
| 161 | ) |
| 162 | |
| 163 | @objc func pageChanged(_ notification: Notification) { |
| 164 | guard let page = pdfView.currentPage, |
| 165 | let doc = pdfView.document else { return } |
| 166 | let index = doc.index(for: page) |
| 167 | pageLabel.text = "Page \(index + 1) of \(doc.pageCount)" |
| 168 | } |
| 169 | ``` |
| 170 | |
| 171 | ## Text Search and Selection |
| 172 | |
| 173 | ### Synchronous Search |
| 174 | |
| 175 | ```swift |
| 176 | let results: [PDFSelection] = document.findString( |
| 177 | "search term", withOptions: [.caseInsensitive] |
| 178 | ) |
| 179 | ``` |
| 180 | |
| 181 | ### Asynchronous Search |
| 182 | |
| 183 | Use `PDFDocumentDelegate` for background searches on large documents. |
| 184 | Implement `didMatchString(_:)` to receive each match and |
| 185 | `documentDidEndDocumentFind(_:)` for completion. |
| 186 | |
| 187 | ### Incremental Search and Fin |