Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Display 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, filling PDF forms, or wrapping PDFView in SwiftUI.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 111% | 0% |
| case-17 | ✓→✓ | = Same ✓ | 294% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 164% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 334% | 0% |
Display, navigate, search, annotate, and manipulate PDF documents with PDFView, PDFDocument, PDFPage, PDFAnnotation, and PDFSelection.
PDFKit requires no entitlements or Info.plist entries.
swiftimport PDFKit
| API | Availability | |---|---| | PDFKit framework | iOS/iPadOS/tvOS 11+, Mac Catalyst 13.1+, macOS 10.4+, visionOS 1.0+ | | Find interaction and page overlays | iOS/iPadOS 16+ |
PDFView renders PDF content and handles zoom, scrolling, text selection, and page navigation.
swiftimport 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) } } }
| 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 |
swiftpdfView.autoScales = true pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit pdfView.maxScaleFactor = 4.0 pdfView.displaysPageBreaks = true pdfView.pageShadowsEnabled = true pdfView.interpolationQuality = .high
PDFDocument loads from a URL, Data, or can be created empty.
swiftlet fileDoc = PDFDocument(url: fileURL) let dataDoc = PDFDocument(data: pdfData) let emptyDoc = PDFDocument()
swiftguard let document = PDFDocument(url: url) else { return } if document.isLocked { if !document.unlock(withPassword: userPassword) { // Show password prompt } }
swiftdocument.write(to: outputURL) document.write(to: outputURL, withOptions: [ .ownerPasswordOption: "ownerPass", .userPasswordOption: "userPass" ]) let data = document.dataRepresentation() // Pages are zero-based. Validate indices; out-of-range calls raise exceptions. let count = document.pageCount document.insert(PDFPage(), at: count) if document.pageCount > 2 { document.removePage(at: 2) } if document.pageCount > 3 { document.exchangePage(at: 0, withPageAt: 3) }
PDFView provides built-in navigation with history tracking.
swift// Go to a specific page let pageIndex = 5 if let document = pdfView.document, pageIndex >= 0, pageIndex < document.pageCount, let page = document.page(at: pageIndex) { 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 the current page if let page = pdfView.currentPage { let destination = PDFDestination(page: page, at: CGPoint(x: 0, y: 500)) pdfView.go(to: destination) }
swiftNotificationCenter.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)" }
swiftlet results: [PDFSelection] = document.findString( "search term", withOptions: [.caseInsensitive] )
Use PDFDocumentDelegate for background searches on large documents. Implement didMatchString(_:) to receive each match and documentDidEndDocumentFind(_:) for completion.
swift// Find next match from current selection let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive]) // System find bar; apply the Setup availability gate pdfView.isFindInteractionEnabled = true
swiftlet fullText = document.string // Entire document let firstPage = document.pageCount > 0 ? document.page(at: 0) : nil let pageText = firstPage?.string // Single page let attributed = firstPage?.attributedString // With formatting // Region-based extraction if let page = firstPage { let selection = page.selection(for: CGRect(x: 50, y: 50, width: 400, height: 200)) let text = selection?.string }
swiftlet 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 are created with PDFAnnotation(bounds:forType:withProperties:) and added to a PDFPage.
swiftfunc 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) }
swiftlet 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)
swiftlet 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)
swiftlet 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)
swiftfor annotation in page.annotations { page.removeAnnotation(annotation) }
Common subtypes include .highlight, .underline, .strikeOut, .text, .freeText, .ink, .link, .line, .square, .circle, .stamp, and .widget.
PDFThumbnailView shows a strip of page thumbnails linked to a PDFView.
swiftlet thumbnailView = PDFThumbnailView() thumbnailView.pdfView = pdfView thumbnailView.thumbnailSize = CGSize(width: 60, height: 80) thumbnailView.layoutMode = .vertical thumbnailView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(thumbnailView)
swiftlet 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) }
Wrap PDFView in a UIViewRepresentable for SwiftUI. PDF-specific wrappers that configure PDFView, pages, annotations, search, thumbnails, or overlays belong in this skill; route only generic representable lifecycle, layout, or SwiftUI state architecture questions to SwiftUI/UIKit interop guidance.
swiftimport 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 } } }
swiftstruct 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.
PDFPageOverlayViewProvider places UIKit views on top of individual pages for interactive controls or custom rendering beyond standard annotations.
swiftclass OverlayProvider: NSObject, PDFPageOverlayViewProvider { func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? { let overlay = UIView() // Add custom subviews return overlay } } class PDFOverlayController: UIViewController { let pdfView = PDFView() private let overlayProvider = OverlayProvider() override func viewDidLoad() { super.viewDidLoad() pdfView.pageOverlayViewProvider = overlayProvider } }
pageOverlayViewProvider is weak, so keep the provider strongly owned. For overlay lifecycle and save handling, read references/pdfkit-patterns.md.
PDFDocument(url:) and PDFDocument(data:) are failable initializers.
swift// WRONG let document = PDFDocument(url: url)! // CORRECT guard let document = PDFDocument(url: url) else { return }
Without autoScales, the PDF renders at its native resolution.
swift// WRONG pdfView.document = document // CORRECT pdfView.autoScales = true pdfView.document = document
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)
PDFKit classes are not thread-safe.
swift// WRONG DispatchQueue.global().async { page.addAnnotation(annotation) } // CORRECT DispatchQueue.main.async { page.addAnnotation(annotation) }
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 init uses optional binding, not force-unwrappdfView.autoScales = true set for proper initial displaypageCount before accessdisplayMode and displayDirection configured to match designisLocked / unlock(withPassword:)!== identity check in updateUIViewPDFViewPageChanged notification observed for page trackingPDFThumbnailView.pdfView linked to the main PDFViewbeginFindString with delegatewrite(to:withOptions:) when encryption neededOther measured skills in the registry, with their headline benchmark lift.