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
Extended patterns for PDFKit: form filling, creating PDFs programmatically, watermarks, printing, merging documents, custom page overlays, document outlines, and custom annotation drawing.
PDF forms use widget annotations. Each widget has a fieldName, a
widgetFieldType, and a widgetStringValue.
func extractFormFields(from document: PDFDocument) -> [(name: String, value: String)] {
var fields: [(String, String)] = []
for pageIndex in 0..<document.pageCount {
guard let page = document.page(at: pageIndex) else { continue }
for annotation in page.annotations {
guard annotation.widgetFieldType == .text,
let name = annotation.fieldName else { continue }
fields.append((name, annotation.widgetStringValue ?? ""))
}
}
return fields
}func fillTextField(in document: PDFDocument, fieldName: String, value: String) {
for pageIndex in 0..<document.pageCount {
guard let page = document.page(at: pageIndex) else { continue }
for annotation in page.annotations {
if annotation.widgetFieldType == .text,
annotation.fieldName == fieldName {
annotation.widgetStringValue = value
return
}
}
}
}func setCheckbox(in document: PDFDocument, fieldName: String, checked: Bool) {
for pageIndex in 0..<document.pageCount {
guard let page = document.page(at: pageIndex) else { continue }
for annotation in page.annotations {
if annotation.widgetFieldType == .button,
annotation.widgetControlType == .checkBoxControl,
annotation.fieldName == fieldName {
annotation.buttonWidgetState = checked ? .onState : .offState
return
}
}
}
}// Text field widget
func createTextField(
bounds: CGRect,
fieldName: String,
placeholder: String = ""
) -> PDFAnnotation {
let widget = PDFAnnotation(bounds: bounds, forType: .widget, withProperties: nil)
widget.widgetFieldType = .text
widget.fieldName = fieldName
widget.widgetStringValue = placeholder
widget.font = UIFont.systemFont(ofSize: 12)
widget.backgroundColor = UIColor.systemGray6
return widget
}
// Checkbox widget
func createCheckbox(bounds: CGRect, fieldName: String) -> PDFAnnotation {
let widget = PDFAnnotation(bounds: bounds, forType: .widget, withProperties: nil)
widget.widgetFieldType = .button
widget.widgetControlType = .checkBoxControl
widget.fieldName = fieldName
widget.buttonWidgetState = .offState
return widget
}
// Radio button widget
func createRadioButton(
bounds: CGRect,
groupName: String,
stateString: String
) -> PDFAnnotation {
let widget = PDFAnnotation(bounds: bounds, forType: .widget, withProperties: nil)
widget.widgetFieldType = .button
widget.widgetControlType = .radioButtonControl
widget.fieldName = groupName
widget.buttonWidgetStateString = stateString
return widget
}
// Choice widget (dropdown)
func createDropdown(
bounds: CGRect,
fieldName: String,
options: [String]
) -> PDFAnnotation {
let widget = PDFAnnotation(bounds: bounds, forType: .widget, withProperties: nil)
widget.widgetFieldType = .choice
widget.fieldName = fieldName
widget.choices = options
widget.isListChoice = false // false = dropdown, true = list box
return widget
}func buildForm() -> PDFDocument {
let document = PDFDocument()
let page = PDFPage()
let pageBounds = CGRect(x: 0, y: 0, width: 612, height: 792) // Letter size
page.setBounds(pageBounds, for: .mediaBox)
// Name field
let nameField = createTextField(
bounds: CGRect(x: 100, y: 700, width: 200, height: 24),
fieldName: "name",
placeholder: ""
)
page.addAnnotation(nameField)
// Agree checkbox
let checkbox = createCheckbox(
bounds: CGRect(x: 100, y: 660, width: 20, height: 20),
fieldName: "agree"
)
page.addAnnotation(checkbox)
document.insert(page, at: 0)
return document
}func createPDFFromImages(_ images: [UIImage]) -> PDFDocument {
let document = PDFDocument()
for (index, image) in images.enumerated() {
if let page = PDFPage(image: image) {
document.insert(page, at: index)
}
}
return document
}func createPDFFromImage(
_ image: UIImage,
mediaBox: CGRect? = nil,
compressionQuality: CGFloat = 0.8
) -> PDFPage? {
var options: [PDFPage.ImageInitializationOption: Any] = [
.compressionQuality: compressionQuality
]
if let box = mediaBox {
options[.mediaBox] = NSValue(cgRect: box)
}
return PDFPage(image: image, options: options)
}For full control over PDF layout, use UIGraphicsPDFRenderer.
func createPDFWithCoreGraphics() -> Data {
let pageRect = CGRect(x: 0, y: 0, width: 612, height: 792)
let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
return renderer.pdfData { context in
context.beginPage()
// Draw title
let title = "Document Title"
let titleAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: 24),
.foregroundColor: UIColor.black
]
title.draw(at: CGPoint(x: 50, y: 50), withAttributes: titleAttributes)
// Draw body text
let body = "This is the body text of the PDF document."
let bodyAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 12),
.foregroundColor: UIColor.darkGray
]
let bodyRect = CGRect(x: 50, y: 100, width: 512, height: 600)
body.draw(in: bodyRect, withAttributes: bodyAttributes)
// Draw a line
context.cgContext.setStrokeColor(UIColor.gray.cgColor)
context.cgContext.setLineWidth(1)
context.cgContext.move(to: CGPoint(x: 50, y: 90))
context.cgContext.addLine(to: CGPoint(x: 562, y: 90))
context.cgContext.strokePath()
}
}let pdfData = createPDFWithCoreGraphics()
let document = PDFDocument(data: pdfData)Add watermarks by subclassing PDFPage and overriding draw(with:to:).
class WatermarkedPage: PDFPage {
var watermarkText: String = "CONFIDENTIAL"
override func draw(with box: PDFDisplayBox, to context: CGContext) {
super.draw(with: box, to: context)
UIGraphicsPushContext(context)
context.saveGState()
let pageBounds = bounds(for: box)
let center = CGPoint(x: pageBounds.midX, y: pageBounds.midY)
// Rotate around center
context.translateBy(x: center.x, y: center.y)
context.rotate(by: -.pi / 4) // -45 degrees
context.translateBy(x: -center.x, y: -center.y)
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: 72),
.foregroundColor: UIColor.red.withAlphaComponent(0.15)
]
let textSize = watermarkText.size(withAttributes: attributes)
let textOrigin = CGPoint(
x: center.x - textSize.width / 2,
y: center.y - textSize.height / 2
)
watermarkText.draw(at: textOrigin, withAttributes: attributes)
context.restoreGState()
UIGraphicsPopContext()
}
}class WatermarkDelegate: NSObject, PDFDocumentDelegate {
func classForPage() -> AnyClass {
WatermarkedPage.self
}
}
// Usage
let delegate = WatermarkDelegate()
document.delegate = delegate
pdfView.document = documentclass ImageWatermarkedPage: PDFPage {
var watermarkImage: UIImage?
override func draw(with box: PDFDisplayBox, to context: CGContext) {
super.draw(with: box, to: context)
guard let image = watermarkImage?.cgImage else { return }
let pageBounds = bounds(for: box)
let imageSize = CGSize(width: 200, height: 200)
let imageRect = CGRect(
x: pageBounds.midX - imageSize.width / 2,
y: pageBounds.midY - imageSize.height / 2,
width: imageSize.width,
height: imageSize.height
)
context.saveGState()
context.setAlpha(0.1)
context.draw(image, in: imageRect)
context.restoreGState()
}
}func mergeDocuments(_ documents: [PDFDocument]) -> PDFDocument {
let merged = PDFDocument()
var insertIndex = 0
for document in documents {
for pageIndex in 0..<document.pageCount {
guard let page = document.page(at: pageIndex) else { continue }
merged.insert(page, at: insertIndex)
insertIndex += 1
}
}
return merged
}func extractPages(
from document: PDFDocument,
range: ClosedRange<Int>
) -> PDFDocument {
let extracted = PDFDocument()
var insertIndex = 0
for pageIndex in range {
guard pageIndex < document.pageCount,
let page = document.page(at: pageIndex) else { continue }
extracted.insert(page, at: insertIndex)
insertIndex += 1
}
return extracted
}func splitDocument(_ document: PDFDocument, pagesPerChunk: Int) -> [PDFDocument] {
var chunks: [PDFDocument] = []
var chunkDoc = PDFDocument()
var chunkIndex = 0
for pageIndex in 0..<document.pageCount {
guard let page = document.page(at: pageIndex) else { continue }
chunkDoc.insert(page, at: chunkIndex)
chunkIndex += 1
if chunkIndex >= pagesPerChunk {
chunks.append(chunkDoc)
chunkDoc = PDFDocument()
chunkIndex = 0
}
}
if chunkDoc.pageCount > 0 {
chunks.append(chunkDoc)
}
return chunks
}func printPDF(document: PDFDocument, from viewController: UIViewController) {
guard let data = document.dataRepresentation() else { return }
let printController = UIPrintInteractionController.shared
let printInfo = UIPrintInfo(dictionary: nil)
printInfo.outputType = .general
printInfo.jobName = "PDF Document"
printController.printInfo = printInfo
printController.printingItem = data
printController.present(animated: true)
}func printPageRange(
document: PDFDocument,
range: ClosedRange<Int>,
from viewController: UIViewController
) {
let subset = extractPages(from: document, range: range)
guard let data = subset.dataRepresentation() else { return }
let printController = UIPrintInteractionController.shared
let printInfo = UIPrintInfo(dictionary: nil)
printInfo.outputType = .general
printController.printInfo = printInfo
printController.printingItem = data
printController.present(animated: true)
}PDFOutline represents the table of contents (bookmarks) of a PDF.
func printOutline(_ outline: PDFOutline, level: Int = 0) {
let indent = String(repeating: " ", count: level)
if let label = outline.label {
print("\(indent)\(label)")
}
for i in 0..<outline.numberOfChildren {
if let child = outline.child(at: i) {
printOutline(child, level: level + 1)
}
}
}
// Usage
if let root = document.outlineRoot {
printOutline(root)
}func buildOutline(for document: PDFDocument) {
let root = PDFOutline()
let chapter1 = PDFOutline()
chapter1.label = "Chapter 1"
if let page = document.page(at: 0) {
chapter1.destination = PDFDestination(page: page, at: .zero)
}
root.insertChild(chapter1, at: 0)
let section1_1 = PDFOutline()
section1_1.label = "Section 1.1"
if let page = document.page(at: 2) {
section1_1.destination = PDFDestination(page: page, at: .zero)
}
chapter1.insertChild(section1_1, at: 0)
let chapter2 = PDFOutline()
chapter2.label = "Chapter 2"
if let page = document.page(at: 5) {
chapter2.destination = PDFDestination(page: page, at: .zero)
}
root.insertChild(chapter2, at: 1)
document.outlineRoot = root
}func goToOutlineEntry(_ outline: PDFOutline, in pdfView: PDFView) {
if let destination = outline.destination {
pdfView.go(to: destination)
} else if let action = outline.action {
pdfView.perform(action)
}
}Override draw(with:in:) to render custom annotation graphics.
class CircleStampAnnotation: PDFAnnotation {
override func draw(with box: PDFDisplayBox, in context: CGContext) {
super.draw(with: box, in: context)
UIGraphicsPushContext(context)
context.saveGState()
let insetBounds = bounds.insetBy(dx: 2, dy: 2)
let path = UIBezierPath(ovalIn: insetBounds)
path.lineWidth = 3
UIColor.systemRed.setStroke()
path.stroke()
// Draw centered text
let text = "REVIEWED"
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: 10),
.foregroundColor: UIColor.systemRed
]
let textSize = text.size(withAttributes: attributes)
let textOrigin = CGPoint(
x: bounds.midX - textSize.width / 2,
y: bounds.midY - textSize.height / 2
)
text.draw(at: textOrigin, withAttributes: attributes)
context.restoreGState()
UIGraphicsPopContext()
}
}class AnnotationDelegate: NSObject, PDFDocumentDelegate {
func `class`(forAnnotationType annotationType: String) -> AnyClass {
switch annotationType {
case "CircleStamp":
return CircleStampAnnotation.self
default:
return PDFAnnotation.self
}
}
}Override draw(with:to:) for page-level custom drawing like headers,
footers, or decorative borders.
class HeaderFooterPage: PDFPage {
var headerText: String = ""
var footerText: String = ""
override func draw(with box: PDFDisplayBox, to context: CGContext) {
super.draw(with: box, to: context)
UIGraphicsPushContext(context)
context.saveGState()
let pageBounds = bounds(for: box)
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 10),
.foregroundColor: UIColor.gray
]
// Header (top of page in PDF coordinates)
if !headerText.isEmpty {
let headerSize = headerText.size(withAttributes: attributes)
let headerOrigin = CGPoint(
x: pageBounds.midX - headerSize.width / 2,
y: pageBounds.maxY - 30
)
headerText.draw(at: headerOrigin, withAttributes: attributes)
}
// Footer (bottom of page in PDF coordinates)
if !footerText.isEmpty {
let footerSize = footerText.size(withAttributes: attributes)
let footerOrigin = CGPoint(
x: pageBounds.midX - footerSize.width / 2,
y: 20
)
footerText.draw(at: footerOrigin, withAttributes: attributes)
}
context.restoreGState()
UIGraphicsPopContext()
}
}func renderPage(_ page: PDFPage, scale: CGFloat = 2.0) -> UIImage? {
let pageBounds = page.bounds(for: .mediaBox)
let size = CGSize(
width: pageBounds.width * scale,
height: pageBounds.height * scale
)
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { ctx in
ctx.cgContext.scaleBy(x: scale, y: scale)
// White background
UIColor.white.setFill()
ctx.fill(CGRect(origin: .zero, size: size))
// PDFPage draw uses bottom-left origin; flip the context
ctx.cgContext.translateBy(x: 0, y: pageBounds.height)
ctx.cgContext.scaleBy(x: 1, y: -1)
page.draw(with: .mediaBox, to: ctx.cgContext)
}
}The simpler thumbnail(of:for:) method handles coordinate flipping
internally.
let thumbnail = page.thumbnail(of: CGSize(width: 200, height: 260), for: .mediaBox)// Rotate a page (must be a multiple of 90)
page.rotation = 90 // 0, 90, 180, or 270Set the crop box to display only a portion of the page.
let pageBounds = page.bounds(for: .mediaBox)
let cropRect = pageBounds.insetBy(dx: 50, dy: 50)
page.setBounds(cropRect, for: .cropBox)Write annotations permanently into the PDF content so they cannot be removed.
func burnInAnnotations(_ document: PDFDocument, to url: URL) -> Bool {
document.write(to: url, withOptions: [
.burnInAnnotationsOption: true
])
}After burning in, annotations become part of the page content and are no longer editable or removable as separate objects.
Check what operations the PDF allows.
func checkPermissions(_ document: PDFDocument) {
let status = document.permissionsStatus // .none, .user, .owner
let canCopy = document.allowsCopying
let canPrint = document.allowsPrinting
let canComment = document.allowsCommenting
let canFillForms = document.allowsFormFieldEntry
let canAssemble = document.allowsDocumentAssembly
let canModify = document.allowsDocumentChanges
let canAccessibility = document.allowsContentAccessibility
}func saveWithRestrictions(_ document: PDFDocument, to url: URL) {
let permissions: PDFAccessPermissions = [
.allowsLowQualityPrinting,
.allowsContentCopying,
.allowsCommenting
]
document.write(to: url, withOptions: [
.ownerPasswordOption: "ownerSecret",
.userPasswordOption: "userPass",
.accessPermissionsOption: permissions.rawValue
])
}A reusable coordinator that handles delegate callbacks, notifications, and annotation hit detection for a SwiftUI-wrapped PDFView.
import SwiftUI
import PDFKit
struct ManagedPDFView: UIViewRepresentable {
let document: PDFDocument
@Binding var currentPageIndex: Int
var onAnnotationTapped: ((PDFAnnotation) -> Void)?
func makeUIView(context: Context) -> PDFView {
let pdfView = PDFView()
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
pdfView.document = document
pdfView.delegate = context.coordinator
return pdfView
}
func updateUIView(_ pdfView: PDFView, context: Context) {
if pdfView.document !== document {
pdfView.document = document
}
if let page = document.page(at: currentPageIndex),
pdfView.currentPage !== page {
pdfView.go(to: page)
}
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, PDFViewDelegate {
let parent: ManagedPDFView
init(_ parent: ManagedPDFView) {
self.parent = parent
super.init()
NotificationCenter.default.addObserver(
self,
selector: #selector(pageChanged),
name: .PDFViewPageChanged,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(annotationHit),
name: .PDFViewAnnotationHit,
object: nil
)
}
@objc func pageChanged(_ notification: Notification) {
guard let pdfView = notification.object as? PDFView,
let page = pdfView.currentPage,
let doc = pdfView.document else { return }
let index = doc.index(for: page)
if parent.currentPageIndex != index {
parent.currentPageIndex = index
}
}
@objc func annotationHit(_ notification: Notification) {
guard let annotation = notification.userInfo?["PDFAnnotationHit"] as? PDFAnnotation
else { return }
parent.onAnnotationTapped?(annotation)
}
func pdfViewWillClick(onLink sender: PDFView, with url: URL) {
UIApplication.shared.open(url)
}
}
}skills
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