Community-maintained Agent Skills for complete Swift and Apple-platform app delivery.
72
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Medium
Suggest reviewing before use
Use this reference when handling document outlines, custom PDFAnnotation and PDFPage subclasses, page overlay views, annotation flattening, and SwiftUI PDFView coordinators.
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()
func destination(for pageIndex: Int) -> PDFDestination? {
guard pageIndex >= 0,
pageIndex < document.pageCount,
let page = document.page(at: pageIndex) else { return nil }
return PDFDestination(page: page, at: .zero)
}
let chapter1 = PDFOutline()
chapter1.label = "Chapter 1"
chapter1.destination = destination(for: 0)
root.insertChild(chapter1, at: 0)
let section1_1 = PDFOutline()
section1_1.label = "Section 1.1"
section1_1.destination = destination(for: 2)
chapter1.insertChild(section1_1, at: 0)
let chapter2 = PDFOutline()
chapter2.label = "Chapter 2"
chapter2.destination = destination(for: 5)
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()
}
}PDFView.pageOverlayViewProvider is weak. Store the provider on a view
controller, SwiftUI coordinator, or another object that outlives the PDFView.
PDFKit requests overlay views for pages it is preparing or displaying, then calls the lifecycle hooks as pages enter and leave the visible range. Keep overlay state outside the view so scrolling does not discard edits.
class PageOverlayProvider: NSObject, PDFPageOverlayViewProvider {
private var pageNotes: [PDFPage: String] = [:]
func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
let textView = UITextView()
textView.text = pageNotes[page] ?? ""
textView.backgroundColor = .clear
return textView
}
func pdfView(
_ view: PDFView,
willEndDisplayingOverlayView overlayView: UIView,
for page: PDFPage
) {
if let textView = overlayView as? UITextView {
pageNotes[page] = textView.text
}
}
}Overlay views are UIKit/AppKit views, not PDF content. Before calling
write(to:) or dataRepresentation(), convert overlay data into PDF
annotations, custom annotation appearance streams, or rendered page content.
Use .burnInAnnotationsOption when saved annotations should become permanent
page content.
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
context.coordinator.attach(to: pdfView)
return pdfView
}
func updateUIView(_ pdfView: PDFView, context: Context) {
if pdfView.document !== document {
pdfView.document = document
}
if currentPageIndex >= 0,
currentPageIndex < document.pageCount,
let page = document.page(at: currentPageIndex),
pdfView.currentPage !== page {
pdfView.go(to: page)
}
}
static func dismantleUIView(_ uiView: PDFView, coordinator: Coordinator) {
NotificationCenter.default.removeObserver(coordinator)
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, PDFViewDelegate {
let parent: ManagedPDFView
init(_ parent: ManagedPDFView) {
self.parent = parent
super.init()
}
func attach(to pdfView: PDFView) {
NotificationCenter.default.removeObserver(self)
NotificationCenter.default.addObserver(
self,
selector: #selector(pageChanged),
name: .PDFViewPageChanged,
object: pdfView
)
NotificationCenter.default.addObserver(
self,
selector: #selector(annotationHit),
name: .PDFViewAnnotationHit,
object: pdfView
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@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)
}
}
}.tessl-plugin
skills
accessorysetupkit
references
activitykit
adattributionkit
references
alarmkit
references
app-clips
app-intents
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
audioaccessorykit
references
authentication
references
avfoundation-audio
references
avkit
background-processing
references
browserenginekit
callkit
references
carplay
cloudkit
contacts-framework
references
core-bluetooth
references
core-data
core-haptics
references
core-motion
references
core-nfc
references
coreml
references
cryptokit
cryptotokenkit
debugging-instruments
device-integrity
references
energykit
references
eventkit
family-controls
references
financekit
focus-engine
gamekit
git-branching-workflow
references
healthkit
references
homekit
references
ios-accessibility
ios-app-workflow
references
ios-ettrace-performance
ios-localization
ios-memgraph-analysis
ios-networking
ios-simulator
references
metrickit
references
musickit
references
natural-language
paperkit
references
passkit
references
pdfkit
pencilkit
references
permissionkit
references
photokit
push-notifications
quicklook
references
realitykit
references
relevancekit
references
scenekit
sensorkit
shazamkit
references
speech-recognition
references
spritekit
storekit
swift-api-design-guidelines
swift-architecture
references
swift-charts
swift-codable
references
swift-code-review
swift-concurrency
swift-formatstyle
references
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-responsive-layout
swiftui-uikit-interop
swiftui-webkit
tabletopkit
tipkit
vision-framework
weatherkit
references