Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Display, navigate, search, annotate, and manipulate PDF documents with
PDFView, PDFDocument, PDFPage, PDFAnnotation, and PDFSelection.
Targets Swift 6.3 / iOS 26+.
PDFKit requires no entitlements or Info.plist entries.
import PDFKitPlatform availability: iOS 11+, iPadOS 11+, Mac Catalyst 13.1+, macOS 10.4+, tvOS 11+, visionOS 1.0+.
PDFView is a UIView subclass that renders PDF content, handles zoom,
scroll, text selection, and page navigation out of the box.
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)
}
}
}| 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 |
pdfView.autoScales = true
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.maxScaleFactor = 4.0
pdfView.displaysPageBreaks = true
pdfView.pageShadowsEnabled = true
pdfView.interpolationQuality = .highPDFDocument loads from a URL, Data, or can be created empty.
let fileDoc = PDFDocument(url: fileURL)
let dataDoc = PDFDocument(data: pdfData)
let emptyDoc = PDFDocument()guard let document = PDFDocument(url: url) else { return }
if document.isLocked {
if !document.unlock(withPassword: userPassword) {
// Show password prompt
}
}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)PDFView provides built-in navigation with history tracking.
// 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)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)"
}let 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.
// Find next match from current selection
let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive])
// System find bar (iOS 16+)
pdfView.isFindInteractionEnabled = truelet 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
}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 are created with PDFAnnotation(bounds:forType:withProperties:)
and added to a PDFPage.
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)
}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)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)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)for annotation in page.annotations {
page.removeAnnotation(annotation)
}| 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) |
PDFThumbnailView shows a strip of page thumbnails linked to a PDFView.
let thumbnailView = PDFThumbnailView()
thumbnailView.pdfView = pdfView
thumbnailView.thumbnailSize = CGSize(width: 60, height: 80)
thumbnailView.layoutMode = .vertical
thumbnailView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(thumbnailView)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)
}Wrap PDFView in a UIViewRepresentable for SwiftUI.
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
}
}
}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.
PDFPageOverlayViewProvider places UIKit views on top of individual pages
for interactive controls or custom rendering beyond standard annotations.
class OverlayProvider: NSObject, PDFPageOverlayViewProvider {
func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
let overlay = UIView()
// Add custom subviews
return overlay
}
}
pdfView.pageOverlayViewProvider = overlayProviderPDFDocument(url:) and PDFDocument(data:) are failable initializers.
// WRONG
let document = PDFDocument(url: url)!
// CORRECT
guard let document = PDFDocument(url: url) else { return }Without autoScales, the PDF renders at its native resolution.
// WRONG
pdfView.document = document
// CORRECT
pdfView.autoScales = true
pdfView.document = documentPDF page coordinates have origin at the bottom-left with Y increasing upward -- opposite of UIKit.
// 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.
// WRONG
DispatchQueue.global().async { page.addAnnotation(annotation) }
// CORRECT
DispatchQueue.main.async { page.addAnnotation(annotation) }PDFDocument is a reference type. Use identity (!==).
// 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 neededskills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references