--- name: pdfkit description: "Display and manipulate PDF documents using PDFKit. Use when embedding PDFView to show PDF files, creating or modifying PDFDocument instances, adding annotations (highlights, notes, signatures), extracting text with PDFSelection, navigating pages, generating thumbnails, filling PDF forms, or wrapping PDFView in SwiftUI." --- # PDFKit Display, navigate, search, annotate, and manipulate PDF documents with `PDFView`, `PDFDocument`, `PDFPage`, `PDFAnnotation`, and `PDFSelection`. Targets Swift 6.3 / iOS 26+. ## Contents - [Setup](#setup) - [Displaying PDFs](#displaying-pdfs) - [Loading Documents](#loading-documents) - [Page Navigation](#page-navigation) - [Text Search and Selection](#text-search-and-selection) - [Annotations](#annotations) - [Thumbnails](#thumbnails) - [SwiftUI Integration](#swiftui-integration) - [Common Mistakes](#common-mistakes) - [Review Checklist](#review-checklist) - [References](#references) ## 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+. ## Displaying PDFs `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) } } } ``` ### Display Modes | Mode | Behavior | |---|---| | `.singlePage` | One page at a time | | `.singlePageContinuous` | Pages stacked vertically, scrollable | | `.twoUp` | Two pages side by side | | `.twoUpContinuous` | 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 = .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() ``` ### Password-Protected PDFs ```swift guard let document = PDFDocument(url: url) else { return } if document.isLocked { if !document.unlock(withPassword: userPassword) { // Show password prompt } } ``` ### 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) ``` ## 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) ``` ### 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)" } ``` ## Text Search and Selection ### Synchronous Search ```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. ### 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 ``` ### 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 } ``` ### 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) } ``` ## Annotations Annotations are created with `PDFAnnotation(bounds:forType:withProperties:)` and added to a `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) } ``` ### 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) ``` ### 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) ``` ### 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) ``` ### Removing Annotations ```swift for annotation in page.annotations { page.removeAnnotation(annotation) } ``` ### Annotation Subtypes Reference | Subtype | Constant | Purpose | |---|---|---| | 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) | ## Thumbnails ### 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) ``` ### Generating Thumbnails Programmatically ```swift let thumbnail = page.thumbnail(of: CGSize(width: 120, height: 160), for: .mediaBox) // All pages let thumbnails = (0.. 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](references/pdfkit-patterns.md). ### Page Overlays (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 ``` ## Common Mistakes ### DON'T: Force-unwrap PDFDocument init `PDFDocument(url:)` and `PDFDocument(data:)` are failable initializers. ```swift // WRONG let document = PDFDocument(url: url)! // CORRECT guard let document = PDFDocument(url: url) else { return } ``` ### DON'T: Forget autoScales on PDFView Without `autoScales`, the PDF renders at its native resolution. ```swift // WRONG pdfView.document = document // CORRECT pdfView.autoScales = true pdfView.document = document ``` ### DON'T: Ignore PDF coordinate system in annotations 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) ``` ### 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) } ``` ### DON'T: Compare PDFDocument with == in UIViewRepresentable `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 } } ``` ## 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 ## References - Extended patterns (forms, watermarks, merging, printing, overlays, outlines, custom drawing): [references/pdfkit-patterns.md](references/pdfkit-patterns.md) - [PDFKit framework](https://sosumi.ai/documentation/pdfkit) - [PDFView](https://sosumi.ai/documentation/pdfkit/pdfview) - [PDFDocument](https://sosumi.ai/documentation/pdfkit/pdfdocument) - [PDFPage](https://sosumi.ai/documentation/pdfkit/pdfpage) - [PDFAnnotation](https://sosumi.ai/documentation/pdfkit/pdfannotation) - [PDFSelection](https://sosumi.ai/documentation/pdfkit/pdfselection) - [PDFThumbnailView](https://sosumi.ai/documentation/pdfkit/pdfthumbnailview) - [PDFPageOverlayViewProvider](https://sosumi.ai/documentation/pdfkit/pdfpageoverlayviewprovider) - [Adding Widgets to a PDF Document](https://sosumi.ai/documentation/pdfkit/adding-widgets-to-a-pdf-document) - [Adding Custom Graphics to a PDF](https://sosumi.ai/documentation/pdfkit/adding-custom-graphics-to-a-pdf)