Convert SwiftUI views to PDF using ImageRenderer in SwiftUI

SPONSORED

glassfy-banner

Your in-app backend just got FREE ⚒️

Glassfy is the ultimate, free SDK for in-app monetization in Swift. Stay ahead with automatic store upgrades and manage subscriptions with ease. No more backend hassle, just revenue growth.

Learn More!
December 14, 2023 6 min readSwiftUI

Apple unveiled a fresh API named ImageRenderer in iOS 16, enabling swift conversion of SwiftUI views into images and .pdf files. This addition simplifies the process of creating top-notch images from your SwiftUI designs.

Let's explore it!

Converting Views to Images

By definition, it is an object that can create / generate images from SwiftUI views.

final class ImageRenderer<Content> where Content : View

To use ImageRenderer, we need to instantiate an instance of it with the content that is a View which we want to convert into an image.

let renderer = ImageRenderer(content: yourView)

Once we have the ImageRenderer instance, we theb can access the generated image using the cgImage or uiImage property from it:

let cgImage = renderer.cgImage
let uiImage = renderer.uiImage

Now let's think what will the usecases of it? We iOS developers usually import or download images then present them inside our application as a view using Image in SwiftUI or UIImage in UIKit.

The most common seen one is sharing something cool that draw in our application. For example:

struct ImageRendererView: View {
    var body: some View {
        let heart = Image(systemName: "heart.fill")
            .resizable()
            .frame(width: 200, height: 200)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .foregroundStyle(.pink.gradient)
            .shadow(color: .black, radius: 5)
        	  .padding()
        	  
        VStack {
            heart
        }
    }
}

Result:

Here we have a beautiful heart image and we want to share it with our love. Then it is a great chance to use ImageRenderer:

VStack {
	heart
            
	let renderer = ImageRenderer(content: heart)
	if let uiImage = renderer.uiImage {
		let image = Image(uiImage: uiImage)
		ShareLink(item: image, preview: SharePreview("Heart", image: image))
	}
}

Also available from iOS 16, ShareLink offer us a native SwiftUI way to share data. Here we use ImageRenderer to generate the heart image from our view, then share it:

0:00
/0:16

When users tap on the share link, it presents a share sheet for users to share content to other applications or copy the data for later use:

Through the combination of drawing on a Canvas and utilizing an ImageRenderer for export, you gain the ability to create images from programmatically-rendered content, encompassing elements such as paths, shapes, gradients, and beyond.

For example that inside our application there is a beautiful flower:

var body: some View {
	let flower = YellowDaisyFlower()
            			.frame(width: 300, height: 300)
        
	VStack {
		flower
            
		let renderer = ImageRenderer(content: flower)
		if let uiImage = renderer.uiImage {
			let image = Image(uiImage: uiImage)
			ShareLink(item: image, preview: SharePreview("Daisy Flower", image: image))
		}
	}
}

Result:

We can then generate to have a beautiful picture from it:

To learn more about Shape and how to leverage its power to draw this beautiful flower, check our article here: Custom Shapes in SwiftUI.

It's worth noting that you can adjust the scale of the rendered image using the scale property of ImageRenderer. By default, the scale is set to 1.0, but you can increase it to generate higher-resolution images.

renderer.scale = 2.0

Rendering Views to PDF

Yes, the ImageRenderer in iOS 16 can render PDF documents. You can use it to convert SwiftUI views into PDF files.

With the render(rasterizationScale: _, renderer: _) method, you can render the designated view onto any CGContext, extending beyond the creation of a rasterized CGImage.

Here's how:

  1. Decide which views to render as PDF.
  2. Create a URL where SwiftUI can write the PDF data.
  3. Instantiate an ImageRenderer instance with the desired view as the content.
  4. Call the render() method on the ImageRenderer to start the rendering process.

Let's see with an example:

struct ImageRendererPDFView: View {
    private var chartView: some View {
        VStack(spacing: 24) {
            PieChartView()

            Text("Monthly Revenue")
            
            Text("January started strong at $6000, followed by a dip in February to $4650, indicating a slight decrease. March saw a significant increase to $6750, marking a recovery or potential growth. April experienced a substantial surge to $8550, reflecting a notable spike in revenue. May dropped to $5500, showing a decline from the previous high in April. June continued the downward trend to $5000, suggesting a second consecutive decrease.")
                .font(.caption)
                .padding(.horizontal)
        }
        .padding()
    }

    var body: some View {
	    GeometryReader { geometry in
			let size = geometry.size
        	chartView
        }
    }
}

Result:

Inside our app, we have a beautiful representaion of monthly revenue data. To learn more about SwiftUI Charts framework, check our article here: Charts in SwiftUI

It would be much better if user can save and export this about as a PDF report. And we will achieve it with ImageRenderer.

We use GeometryReader here to get the size provided to our chartView which we will then use to generate the same specific PDF size.

We will generate our chartView to PDF and save it to app sandbox with the help of FileManager.

let url = FileManager.default
	.urls(for: .documentDirectory, in: .userDomainMask)
	.first!
	.appending(path: "SaleChart.pdf")

This create a URL pointing to a file named "SaleChart.pdf" within the document directory of the app's sandboxed file system.

Next, we initilize our renderer instance passing the chartView as the content with specific size received from GeometryReader:

let renderer = ImageRenderer(content: chartView.frame(width: size.width, height: size.height, alignment: .center))

Then we set up a Core Graphics context for drawing into a PDF file at the previously defined url:

if let consumer = CGDataConsumer(url: url as CFURL), let context = CGContext(consumer: consumer, mediaBox: nil, nil) {
    renderer.render { size, renderer in
        var mediaBox = CGRect(origin: .zero, size: size)
        // Drawing PDF
        context.beginPage(mediaBox: &mediaBox)
        renderer(context)
        context.endPDFPage()
        context.closePDF()
        // Updating the PDF URL
        print(NSHomeDirectory())
    }
}

ImageRenderer will render the content of the chartView into the PDF context.

Let's generate our PDF simply when our view appear:

var body: some View {
        GeometryReader { geometry in
            let size = geometry.size
             chartView
                .onAppear {
                    let url = FileManager.default
                        .urls(for: .documentDirectory, in: .userDomainMask)
                        .first!
                        .appending(path: "SaleChart.pdf")
                    
                    let renderer = ImageRenderer(content: chartView.frame(width: size.width, height: size.height, alignment: .center))
                    
                    // Generating PDF
                    if let consumer = CGDataConsumer(url: url as CFURL), let context = CGContext(consumer: consumer, mediaBox: nil, nil) {
                        renderer.render { size, renderer in
                            var mediaBox = CGRect(origin: .zero, size: size)
                            // Drawing PDF
                            context.beginPage(mediaBox: &mediaBox)
                            renderer(context)
                            context.endPDFPage()
                            context.closePDF()
                            // App's home directory.
                            print(NSHomeDirectory())
                        }
                    }
                }
        }
    }

Run it on the simulator and you can see App's home directory being printed out after the process finished. Copy that and paste to the Terminal with the open command to see the specific location:

Hola we a beautiful PDF that can be share and mail to our boss now:

Conclusion

ImageRenderer significantly simplifies the process of converting SwiftUI views into images and PDF documents. You have already learned all about it in this article.

Its integration enables developers to easily create visual assets from SwiftUI designs, empowering them to share, export, and present content in a more visually appealing and versatile manner.

Important

It doesn't render views from native platform frameworks like AppKit and UIKit, such as web views, media players, and certain controls. In place of these unrendered views, ImageRenderer shows a placeholder image.

We have launched our new e-book "Cracking the iOS Interview" with Top 100 iOS Interview Questions & Answers. Our book has helped more than 482 iOS developers in successfully cracking their iOS Interviews.

Grab your copy now and rock your next iOS Interview!

Want latest weekly iOS updates?

Sign up for our newsletter.