The problem
On iOS 27, UIPrintInteractionController.print(to:completionHandler:) works on its first call in a process. Every subsequent call is accepted (print(to:) returns true and printInteractionControllerWillStartJob fires), but the completion handler is never invoked: nothing prints, didFinishJob never fires, no error is produced, and no job appears in Print Center.
I expected each call to submit an independent job and invoke its completion handler, as it does on iOS 26 and earlier with the identical binary.
Steps to reproduce
- Build an app that prints without the print panel, using a retained
UIPrinterfromUIPrinterPickerController. - Submit a job:
let controller = UIPrintInteractionController.shared
let info = UIPrintInfo(dictionary: nil)
info.outputType = .general
info.jobName = "job-\(n)"
controller.printInfo = info
controller.printingItem = pdfData // valid PDF, canPrint(data) == true
controller.delegate = self
let started = controller.print(to: savedPrinter) { _, completed, error in
print("completion", completed, String(describing: error))
}
print("started", started)
- First job:
started == true, bothwillStartJobanddidFinishJobfire, the completion handler runs withcompleted == true, and the page prints. - Submit a second job the same way, same process.
started == trueandwillStartJobfires withprintInfo.printerIDpopulated andprintPapernegotiated to the same values as the successful job. Then nothing. NodidFinishJob, no completion handler, no error, no page, nothing in Print Center.- A third job:
print(to:)returnsfalsesynchronously, since the shared controller still considers job two in flight.
System information
- iPad, iPadOS 27.0 developer beta 6
- Built against iOS 26.2 SDK (Xcode 26.2). Same binary works on iOS 26.
- Reproduced on two unrelated printers: Brother QL-820NWB and Canon MF420 Series.
- Content is a small PDF (~11 to 13 KB);
canPrintData:returnstrue.
Troubleshooting tried
- Restarted the iPad and reinstalled the app. Each buys exactly one successful print before the failure returns.
- Compared observable state between the working and hung jobs. Identical in every respect.
- During a hang, printed the same content to the same printer from Files via the share sheet. Worked normally, including on repeat.
- Paused in the debugger during the hang. No blocked thread; no PrintKit frame, XPC wait, or semaphore anywhere in
bt all. - Called
contactPrinterbefore each job on the retained printer. Returnedtruequickly, and the job still hung. - Implemented
printInteractionControllerParentViewController(_:)in case a presentation context was missing. UIKit never calls it on this path.
Workaround
Creating a new UIPrintInteractionController per job resolves it completely. A long-lived instance the app creates itself fails exactly like .shared does, so the issue is reuse of a controller that has already run a job, not the shared singleton specifically.
Is per-job instantiation the supported pattern? The docs say the shared instance "represents a print job" but don't state whether a controller may be reused for a second job, and every sample I can find uses sharedPrintController with the present… methods rather than print(to:).