I've run into what appears to be a bug in PDFKit's radio button serialization.
When creating a radio button group with PDFAnnotation, only the first annotation added via page.addAnnotation() gets a correct /AS entry in the written PDF — all other annotations always get /AS /Off, regardless of buttonWidgetState.
Minimal reproduction
func makeRadioButton(optionId: String, isSelected: Bool) -> PDFAnnotation {
let ann = PDFAnnotation(bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
forType: .widget, withProperties: nil)
ann.widgetFieldType = .button
ann.widgetControlType = .radioButtonControl
ann.fieldName = "Choice"
ann.buttonWidgetStateString = optionId
ann.buttonWidgetState = isSelected ? .onState : .offState
return ann
}
let pdf = PDFDocument()
let page = PDFPage()
pdf.insert(page, at: 0)
// Intend to select B
page.addAnnotation(makeRadioButton(optionId: "A", isSelected: false))
page.addAnnotation(makeRadioButton(optionId: "B", isSelected: true))
page.addAnnotation(makeRadioButton(optionId: "C", isSelected: false))
_ = pdf.dataRepresentation()
// Result: /AS is /Off for all three — B is not selected in the PDF
What I observed
Selecting A (first annotation added): /AS /A written correctly works
Selecting B or C: /AS /Off for all buttons
Additionally, dataRepresentation() corrupts the in-memory state as a side effect: buttonWidgetState of the selected annotation is .onState before the call and .offState after.
Root cause
During serialization, dataRepresentation() internally calls setButtonWidgetState:.onState on each annotation in turn to generate appearance streams. This triggers PDFKit's radio-group exclusivity logic, which silently clears all other annotations — so by the time /AS is written, only the first annotation's selection survives.
Workaround
It took a while to track this down, so I'm documenting the workaround here in case it helps others.
Add the annotation that should be selected first via page.addAnnotation():
// Add selected annotation first
page.addAnnotation(makeRadioButton(optionId: "B", isSelected: true))
page.addAnnotation(makeRadioButton(optionId: "A", isSelected: false))
page.addAnnotation(makeRadioButton(optionId: "C", isSelected: false))
Tested on macOS 26.3 / Xcode 26.3. Filed as Feedback FB22167174.
Full code including workaround is here:
radio_bug_swift.txt
Has anyone else hit this? Is there a cleaner method I'm missing?
PDFKit
RSS for tagDisplay and manipulate PDF documents in your applications using PDFKit.
Posts under PDFKit tag
30 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
In Swift, iOS, I have a pdf file.
I want to take the pages 2 by 2 and put them side by side, on the new page.
For this, I have to scale initial pages half size and rotate .pi/2.
I managed to achieve this by converting pdf pages to UIImages and using a UIGraphicsImageRenderer. But with a critical loss of resolution.
I've tried improving the resolution by creating images as jpegData(withCompressionQuality: 1.0), to no avail.
So I would need to work directly on the pdf pages using CGPDFDocument format.
The code structure is as follows, to insert a single scaled page:
for iPage in … {
if let _page = theCGPdfDocument.page(at: 1) {
var _pageRect: CGRect = _page.getBoxRect(CGPDFBox.mediaBox)
writeContextInDestination!.beginPage(mediaBox: &_pageRect)
// translate to compensate for the flip caused displacement
writeContextInDestination!.translateBy(x: _pageRect.size.width, y: _pageRect.size.height)
Scale (-1, -1) // rotate 180°
// add rotate as needed
writeContextInDestination!.scaleBy(x: -1, y: -1)
writeContextInDestination!.clip(to: _pageRect)
writeContextInDestination!.drawPDFPage(_page)
writeContextInDestination!.endPage() // end the current page
}
}
writeContextInDestination!.closePDF()
But I do not succeed in inserting the drawing of the second page.
I've tried repeating lines 7 to 11 for a second page at line 13. No success.
What is the direction to look for ?
I have a MacOS app which displays PDFs, and I want to create a New document from the Clipboard, if the clipboard contains valid graphical data.
My problem is that even if it doesn't, I still get a blank new document window. AppKit always creates a new document.
I've tried overriding the newDocument function; I've tried avoiding the built-in functions altogether.
Are there any general recommendations for going about this?
In PDFKit on MacOS, if you create a PDFView and matching Thumbnail View, and then set the PDFView to Horizontal: selecting the thumbnail no longer move the PDFView to the correct page.
It works for all styles of View (Two Page Continuous, etc) in Vertical orientation.
Hello,
We are experiencing on some occasions a wrong behavior with PDFDocument method:
func page(at index: Int) -> PDFPage?
With certain PDF files, this method returns the wrong PDFPage.
This occurs on iOS 18.3, 18.5 and 18.6.2 (an maybe on other versions).
Try this PDF for instance (page 81 is returned when index = 2):
https://drive.google.com/open?id=1MHm2wjfsbWB8OiRmARUMmvODYxp4DIqP&usp=drive_fs
Also, I mention that this doesn't occur systematically with this PDF. When making a copy of this file we don't observe the issue.
Could this be linked some kind of internal cache issue ?
I'm experiencing an issue with PDFKit where page.removeAnnotation(annotation) successfully removes the annotation from the page's data structure, but the PDFView no longer updates automatically to reflect the change visually.
Issue Details:
The annotation is removed (verified by checking page.annotations.count)
The PDFView display doesn't refresh to show the removal
This code was working correctly before and suddenly stopped working
No code changes were made on my end
Hey there, I have a slight problem which is also known on the web, but I couldn't find any answers...
My iOS App is working with documents like pdf and images. It is made to handle invoices and upload them to an external service. The app's job is to compress those pdfs and images. Sadly the PDFKit's PDFDocument increases its size just after importing it with pdfDocument = PDFDocument(url: url) and later exporting it to an pdf file again with data = pdf.dataRepresentation() without ever modifying it.
It increases by server KB after each export. Do you have any alternatives to the renderer dataRepresentation() or a general PDFKit alternative
Thanks
Dear Apple Developer Team,
On iOS 26, the contents of PDF pages appear to be swapped.
Could you please advise if there is a workaround or a planned fix for this issue?
Steps to Reproduce:
Download the attached PDF on iOS 26.
Open the PDF in the Files app.
Tap the PDF to view it in Quick Look.
Navigate to page 5.
Expected Result:
The page number displayed at the bottom should be 5.
Actual Result:
The page number displayed at the bottom is 4.
Issue:
This is not limited to page 5—multiple page contents appear to be swapped.
I have also submitted feedback via Feedback Assistant (FB20743531) on October 20.
Best regards,
Yoshihito Suezawa
Hi everyone,
I faced an issue that on IOS 26 removeAnnotation method doesn't remove annotation. This code worked on previous versions (IOS 18, 17) but suddenly stopped working on IOS 26.
Has anyone faced this issue?
guard let document = await pdfView.document else { return }
for pageIndex in 0..<document.pageCount {
guard let page = document.page(at: pageIndex) else { continue }
let annotations = page.annotations
for annotation in annotations {
page.removeAnnotation(annotation)
}
}
Looking for any method to quickly flatten a PDF without opening Preview and without installing 3 party software. Any ideas?
Save as PDF in Preview works, but I don't want to have to open Preview each time I need to do this.
The Create PDF action which appears in Finder when you select 2 or more PDFs flattens PDFs, but it requires me to select 2 or more files, and I generally don't want to combine PDFs--I simply wish to flatten a PDF.
Most Automator and Shortcuts options I am aware of do not flatten PDFs, and in some cases, strip out form field data from PDFs.
I was able to mark annotations outside the page before iOS 26 update in PDFkit, but after the iOS 26 update i am able to mark the annotations(which are not visible) outside the page and save them on DB, but unable to render(show) them.
My IOS app generates pdf files.
Every time my users open the generated pdf files, the autofill popup jumps out, but my pdf file is NOT for interacting.
I'm here to ask if there's a way to mark my pdf files as "not a form", like in metadata or anywhere else?
Getting this crash after I do this in PDFKit a lot:
PDFSelection *nextSelect = [self.pdfView.document findString:currentSearchString fromSelection:currentSelction withOptions:NSCaseInsensitiveSearch];
if (nextSelect != nil)
{
self.pdfView.currentSelection = nextSelect;
[self.pdfView scrollSelectionToVisible:nil];
}
Which often leads to:
0 CoreFoundation 0x000000019ced4770 __exceptionPreprocess + 176
1 libobjc.A.dylib 0x000000019c9b2418 objc_exception_throw + 88
2 CoreFoundation 0x000000019cfffe10 -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 724
3 CoreFoundation 0x000000019cfa1ae4 +[NSDictionary dictionaryWithObjects:forKeys:count:] + 52
4 PDFKit 0x00000001cb56e0fc -[PDFView _axPostPageChangeNotification:] + 348
5 Foundation 0x000000019e6a25e4 __NSFireDelayedPerform + 372
6 CoreFoundation 0x000000019ce92290 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 32
7 CoreFoundation 0x000000019ce91f50 __CFRunLoopDoTimer +
Hello,
I’m using PDFKit to display PDFs with a large number of annotations. However, when there are many annotations, I’m experiencing serious performance issues while zooming in/out with PDFView.
• During pinch zoom, it seems like continuous re-rendering occurs.
• Memory usage spikes dramatically while zooming, then drops back down repeatedly.
• As a result, zooming feels laggy and not smooth.
What I’d like to achieve is the following:
1. Prevent unnecessary re-rendering while zooming is in progress.
2. Trigger a single re-render only once the zoom gesture ends (e.g., in scrollViewDidEndZooming).
3. At the very least, avoid the memory spikes during zoom.
Is there any way to control how annotations are re-drawn during zooming in PDFKit, or to throttle/debounce rendering so it happens only after the gesture completes?
I’d really appreciate any advice from others who have encountered similar issues, or guidance from Apple on the recommended approach.
Thanks in advance!
Hi, I’m currently working with PDFKit and using PDFView to display documents.
I’m adding free-drawing content as annotations, but I’ve noticed that these annotations are always rendered on top of the text, which ends up covering the text and making it hard to read.
What I would like to do is render these drawing annotations behind the text layer, so that the document text remains visible above the drawing.
Has anyone found a way to achieve this, or is there a recommended approach for controlling the annotation rendering order in PDFKit?
Thanks in advance!
Same PDF renders differently when open in Chrome, Safari; Apple Preview, Acrobat.
on Apple Preview, Safari - the PDF appears correctly for a second or two and then appears washed out.
Our app uses Safari to render PDFs and our users are complaining that scanned PDFs are not rendering properly.
How do I fix this issue (Swift, Obj-C)?
Hi!
I'm using PDFKits PdfView to display a PDF file and after several page changes, the background turns black, suddenly (like a big black rectangle). The error occurs in the Books App on the iPad as well and looks similiar to this issue:
https://discussions.apple.com/thread/8627073?sortBy=rank
Anyone got a solution for this?
I ave an application that makes use of charts. I would like to have a button for the user to save the chart as a PDF. I tried to have the button save the PDF to the user's document directory directly. That attempt failed. But I was able to save the PDF to the application sandboxed documents directory. The question is how to programmatically move that file from the application documents folder to the user's general documents folder. So far I have not been able to find a method that will move the PDF file. Any ideas?
I’m attempting to display a PDF file in a visionOS application using the PDFView in PDFKit.
When running on device with visionOS 26, a horizontal solid line appears on some pages, while on other pages, both a horizontal and vertical solid line appear.
These lines do not appear
in Xcode preview canvas (macOS, visionOS)
on device running visionOS 2.5
on Mac running macOS 15.6
I thought that this could possibly be the page breaks, but setting displaysPageBreaks = false did not appear to be effective.
Are there any other settings that could be causing the lines to display?
Code Example
struct ContentView: View {
@State var pdf: PDFDocument? = nil
var body: some View {
PDFViewWrapper(pdf: pdf)
.padding()
}
}
#Preview(windowStyle: .automatic) {
ContentView(pdf: PDFDocument(url: Bundle.main.url(forResource: "SampleApple", withExtension: "pdf")!))
.environment(AppModel())
}
struct PDFViewWrapper: UIViewRepresentable {
let pdf: PDFDocument?
func makeUIView(context: Context) -> PDFView {
let view = PDFView()
view.document = pdf
view.displaysPageBreaks = false
return view
}
func updateUIView(_ uiView: PDFView, context: Context) {
uiView.document = pdf
}
}
Tested with
Xcode Version 16.4 (16F6)
Xcode Version 26.0 beta 5 (17A5295f)
visionOS 2.5
visionOS 26 Beta 5
I
By setting the PKCanvasView background color to blue, I can tell that the PKCanvasView for each PDFPage is created normally, but it does not respond to touch. Specifically, whether it is finger or applepencil, all the responses of the page occur from PDFView(such as zoom and scroll), and PKCanvasView can not draw, please how to solve?
class PDFAnnotatableViewController: UIViewController, PDFViewDelegate {
private let pdfView = PDFView()
private var pdfDocument: PDFDocument?
let file: FileItem
private var userSettings: UserSettings
@Binding var selectedPage: Int
@Binding var currentMode: Mode
@Binding var latestPdfChatResponse: LatestPDFChatResponse
@State private var pdfPageCoordinator = PDFPageCoordinator()
@ObservedObject var userMessage: ChatMessage
init(file: FileItem,
userSettings: UserSettings,
drawDataList: Binding<[DrawDataItem]>,
selectedPage: Binding<Int>,
currentMode: Binding<Mode>,
latestPdfChatResponse: Binding<LatestPDFChatResponse>,
userMessage: ChatMessage) {
self.file = file
self.userSettings = userSettings
self._selectedPage = selectedPage
self._currentMode = currentMode
self._latestPdfChatResponse = latestPdfChatResponse
self.userMessage = userMessage
super.init(nibName: nil, bundle: nil)
DispatchQueue.global(qos: .userInitiated).async {
if let document = PDFDocument(url: file.pdfLocalUrl) {
DispatchQueue.main.async {
self.pdfDocument = document
self.pdfView.document = document
self.goToPage(selectedPage: selectedPage.wrappedValue - 1)
}
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupPDFView()
}
private func setupPDFView() {
pdfView.delegate = self
pdfView.autoScales = true
pdfView.displayMode = .singlePage
pdfView.displayDirection = .vertical
pdfView.backgroundColor = .white
pdfView.usePageViewController(true)
pdfView.displaysPageBreaks = false
pdfView.displaysAsBook = false
pdfView.minScaleFactor = 0.8
pdfView.maxScaleFactor = 3.5
pdfView.pageOverlayViewProvider = pdfPageCoordinator
if let document = pdfDocument {
pdfView.document = document
goToPage(selectedPage: selectedPage)
}
pdfView.frame = view.bounds
pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(pdfView)
NotificationCenter.default.addObserver(
self,
selector: #selector(handlePageChange),
name: .PDFViewPageChanged,
object: pdfView
)
}
// Dealing with page turning
@objc private func handlePageChange(notification: Notification) {
guard let currentPage = pdfView.currentPage, let document = pdfView.document else { return }
let currentPageIndex = document.index(for: currentPage)
if currentPageIndex != selectedPage - 1 {
DispatchQueue.main.async {
self.selectedPage = currentPageIndex + 1
}
}
}
func goToPage(selectedPage: Int) {
guard let document = pdfView.document else { return }
if let page = document.page(at: selectedPage) {
pdfView.go(to: page)
}
}
// Switch function
func togglecurrentMode(currentMode: Mode){
DispatchQueue.main.async {
if self.currentMode == .none{
self.pdfView.usePageViewController(true)
self.pdfView.isUserInteractionEnabled = true
} else if self.currentMode == .annotation {
if let page = self.pdfView.currentPage {
if let canvasView = self.pdfPageCoordinator.getCanvasView(forPage: page) {
canvasView.isUserInteractionEnabled = true
canvasView.tool = PKInkingTool(.pen, color: .red, width: 20)
canvasView.drawingPolicy = .anyInput
canvasView.setNeedsDisplay()
}
}
}
}
}
}
class MyPDFPage: PDFPage {
var drawing: PKDrawing?
func setDrawing(_ drawing: PKDrawing) {
self.drawing = drawing
}
func getDrawing() -> PKDrawing? {
return self.drawing
}
}
class PDFPageCoordinator: NSObject, PDFPageOverlayViewProvider {
var pageToViewMapping = [PDFPage: PKCanvasView]()
func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
var resultView: PKCanvasView? = nil
if let overlayView = pageToViewMapping[page] {
resultView = overlayView
} else {
let canvasView = PKCanvasView(frame: view.bounds)
canvasView.drawingPolicy = .anyInput
canvasView.tool = PKInkingTool(.pen, color: .systemYellow, width: 20)
canvasView.backgroundColor = .blue
pageToViewMapping[page] = canvasView
resultView = canvasView
}
if let page = page as? MyPDFPage, let drawing = page.drawing {
resultView?.drawing = drawing
}
return resultView
}
func pdfView(_ pdfView: PDFView, willEndDisplayingOverlayView overlayView: UIView, for page: PDFPage) {
guard let overlayView = overlayView as? PKCanvasView, let page = page as? MyPDFPage else { return }
page.drawing = overlayView.drawing
pageToViewMapping.removeValue(forKey: page)
}
func savePDFDocument(_ pdfDocument: PDFDocument) -> Data {
for i in 0..<pdfDocument.pageCount {
if let page = pdfDocument.page(at: i) as? MyPDFPage, let drawing = page.drawing {
let newAnnotation = PDFAnnotation(bounds: drawing.bounds, forType: .stamp, withProperties: nil)
let codedData = try! NSKeyedArchiver.archivedData(withRootObject: drawing, requiringSecureCoding: true)
newAnnotation.setValue(codedData, forAnnotationKey: PDFAnnotationKey(rawValue: "drawingData"))
page.addAnnotation(newAnnotation)
}
}
let options = [PDFDocumentWriteOption.burnInAnnotationsOption: true]
if let resultData = pdfDocument.dataRepresentation(options: options) {
return resultData
}
return Data()
}
func getCanvasView(forPage page: PDFPage) -> PKCanvasView? {
return pageToViewMapping[page]
}
}
Is there an error in my code? Please tell me how to make PKCanvasView painting normally?