If you are an iOS Developer in the Apple ecosystem, you’ve likely faced the challenge of generating documents, reports, invoices, or receipts directly from your application. Historically, creating a PDF required dealing with complex CoreGraphics APIs or wrapping views in a UIHostingController using UIKit. Fortunately, modern Swift programming has simplified this enormously.
In this article, we are going to dive deep into how to render a SwiftUI view to PDF using the ImageRenderer class. I will guide you step-by-step so you can implement this functionality in your Xcode projects, creating clean and efficient Swift code that will work across the Apple ecosystem (iOS, macOS, and watchOS).
What is ImageRenderer in SwiftUI?
Introduced in iOS 16 and macOS 13, ImageRenderer is a native SwiftUI class that takes any declarative view and converts it into pixel data (an image) or a vector drawing context (like a PDF document).
Unlike older solutions, ImageRenderer understands the SwiftUI layout system, respecting environment modifiers, custom fonts, and color schemes. For an iOS Developer, this means the exact same view you design in Xcode to be displayed on screen can be faithfully exported to a printable or shareable document.
Prerequisites
Before we start writing code, make sure your environment is ready:
- IDE: Xcode 14.0 or higher.
- Language: Swift 5.7 or higher.
- Operating Systems: iOS 16.0+, macOS 13.0+, or watchOS 9.0+.
Step 1: Design the View to Render
To render a SwiftUI view to PDF, we first need a view. In Swift programming, the views that are going to be exported are usually not the same as the interactive views of the application, since PDFs require white backgrounds, readable typography, and don’t need interactive buttons.
Let’s create a simple invoice:
import SwiftUI
struct InvoiceView: View {
let clientName: String
let total: Double
let date = Date()
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
Image(systemName: "applelogo")
.font(.largeTitle)
Spacer()
Text("INVOICE")
.font(.largeTitle)
.bold()
.foregroundColor(.blue)
}
Divider()
Text("Date: \(date.formatted(date: .abbreviated, time: .omitted))")
Text("Client: \(clientName)")
.font(.title3)
Spacer()
HStack {
Text("Total to pay:")
.font(.title2)
Spacer()
Text(String(format: "$%.2f", total))
.font(.title2)
.bold()
}
}
.padding()
// It is crucial to define a fixed size or an ideal frame for the PDF
.frame(width: 595, height: 842) // Standard A4 size at 72 DPI
.background(Color.white)
}
}
Note for the iOS Developer: Notice the
.frame(width: 595, height: 842)modifier. Unlike an iPhone or Mac screen which are dynamic, a PDF document usually requires specific physical or logical dimensions (such as A4 or Letter format).
Step 2: The Logic to Render a SwiftUI View to PDF
This is where the magic of Swift comes in. ImageRenderer requires running on the Main Thread because it interacts directly with the UI rendering system. Therefore, our function must be marked with @MainActor.
Next, we will create a utility class to handle the export.
import SwiftUI
import CoreGraphics
@MainActor
class PDFCreator {
/// Generates a PDF from a SwiftUI view and returns the URL of the temporary file
static func generatePDF<V: View>(from view: V, fileName: String) -> URL? {
// 1. Instantiate the ImageRenderer with our view
let renderer = ImageRenderer(content: view)
// 2. Define the path where we will save the PDF (Temporary directory)
let tempDirectory = FileManager.default.temporaryDirectory
let fileURL = tempDirectory.appendingPathComponent("\(fileName).pdf")
// 3. Use the render method with a CoreGraphics context
renderer.render { size, renderContext in
// We define the "box" or boundaries of our document
var mediaBox = CGRect(x: 0, y: 0, width: size.width, height: size.height)
// We create the PDF context pointing to our URL
guard let pdfContext = CGContext(fileURL as CFURL, mediaBox: &mediaBox, nil) else {
return
}
// 4. Start the PDF page
pdfContext.beginPDFPage(nil)
// 5. Draw the SwiftUI view inside the PDF context
renderContext(pdfContext)
// 6. Close the page and the document
pdfContext.endPDFPage()
pdfContext.closePDF()
}
return fileURL
}
}
What is happening in this code?
ImageRenderer(content:): We pass it the SwiftUI view.render { size, renderContext in ... }: This is the key. We are not asking for an image (as we would withrenderer.uiImage), but we are accessing the CoreGraphics drawing block.CGContext: This is Apple’s low-level API. By callingrenderContext(pdfContext), we tell the SwiftUI renderer to paint all its elements (texts, shapes, images) inside our PDF file.
Step 3: User Interface Integration and Cross-Platform
Now that we have our rendering engine, let’s create a main view in Xcode where the user can preview the data and tap a button to export the PDF.
One of the beauties of modern Swift programming is that we can use components like ShareLink to handle the generated file universally on iOS, macOS, and even watchOS (though on the watch, sharing options are more limited).
struct ContentView: View {
@State private var pdfURL: URL?
var body: some View {
VStack(spacing: 30) {
Text("Invoice Generator")
.font(.title)
.bold()
// Button to generate the PDF
Button(action: {
preparePDF()
}) {
Text("Generate PDF")
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(10)
}
// If the PDF has already been generated, we show the share button
if let url = pdfURL {
ShareLink(item: url) {
Label("Share or Save Invoice", systemImage: "square.and.arrow.up")
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}
.padding()
}
// Function executed on the MainActor
@MainActor
private func preparePDF() {
let invoice = InvoiceView(clientName: "John Doe", total: 1450.00)
self.pdfURL = PDFCreator.generatePDF(from: invoice, fileName: "Invoice_JohnDoe")
}
}
Advanced Considerations for the Developer
When you render a SwiftUI view to PDF, there are certain physical and technical limitations that every iOS Developer must keep in mind:
1. Pagination
The ImageRenderer renders the view exactly as it is. If your InvoiceView is longer than an A4 page, the content will simply be cut off or create a giant, non-standard sized PDF. SwiftUI does not automatically divide long lists into multiple PDF pages. If you need multi-page reports, you will have to divide your data programmatically, iterate over it, call pdfContext.beginPDFPage(nil) multiple times, and render sub-views per page.
2. Asynchronous Resources
If your view contains AsyncImage or images downloaded from the internet, ImageRenderer will not wait for them to load. It will capture the view in its “loading” state (probably a gray or empty box). Make sure all data and images are fully loaded into memory before instantiating the renderer.
3. watchOS Considerations
Although Swift and ImageRenderer support watchOS 9+, handling files (like PDFs) is unusual on an Apple Watch. You can generate the file in memory, but the way the user interacts with it (or how you send it via WatchConnectivity to the paired iPhone) requires a different architectural flow than iOS or macOS.
Conclusion
The ability to render a SwiftUI view to PDF using ImageRenderer eliminates thousands of lines of repetitive CoreGraphics code from our applications. This tool strengthens the declarative paradigm, allowing an iOS Developer to use their UI design knowledge directly for generating structured documents.
If you have any questions about this article, please contact me and I will be happy to help you . You can contact me on my X profile or on my Instagram profile.