Create a PDF in SwiftUI using PDFKit from Dynamic Data

To generate a PDF in SwiftUI using PDFKit, you can follow these steps:

  1. Import PDFKit: Make sure you have imported PDFKit in your Swift file.
  2. Render PDF Content: Define and render the content you want to include in your PDF using UIGraphicsPDFRenderer. You can add text, images, or custom views into PDF.
  3. Generate PDF: Add rendered data into a PDF Document.

Here is a complete example:

import SwiftUI
import PDFKit

struct ContentView: View {
  
    var body: some View {
        Button("Generate PDF") {
            generatePDF()
        }
    }
    
    func generatePDF() {
        let pageRect = CGRect(x: 0, y: 0, width: 612, height: 792) // US Letter size
        let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
        // 1. adding a temporary pdf file
        let url = FileManager.default.temporaryDirectory.appendingPathComponent("output.pdf")
        
        // 2. attributes for text like size
        let attributes = [
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12)
        ]
        
        do {
            try renderer.writePDF(to: url) { context in
                // 3.  start a pdf page
                context.beginPage()

                let text = "I'm a PDF!"
                // 4. postion of text in the pdf page
                text.draw(at: CGPoint(x: 20, y: 50), withAttributes: attributes)
                
            }
            print("PDF saved at: \(url.path)")
        } catch {
            print("Error generating PDF: \(error)")
        }
    }
}

If you run the app and click on the Generate PDF button, the PDF URL will be printed in the console like this:

 /Users/name/Library/Developer/CoreSimulator/Devices/.../data/Containers/Data/Application/.../tmp/output.pdf

You can copy and paste this URL to any browser to view the PDF. You can also view this PDF inside your app. We will see that later.

You can add an image like this:

            // adding image from SF Symbols
            let globeIcon = UIImage(systemName: "globe")
            let globeIconRect = CGRect(x: 50, y: 100, width: 100, height: 100)
            globeIcon!.draw(in: globeIconRect)

If we want to add multiple items like text, images, etc, we need to set the position of each item. PDFKit will not add items one after another like regular text processor software. Calculating the position of each item manually is not a good idea. So we have to calculate the position dynamically.

Let’s say we have a Person structure like this:

struct Person: Identifiable {
    let id = UUID()
    let name: String
    let number: String
    let address: String
}

We want to create a PDF page from the list of people. We can do that like this:

                for person in people {
                    let personContent = """
                             Name: \(person.name)
                             Number: \(person.number)
                             Address: \(person.address)
                             """
                    
                    let attributedString = NSAttributedString(string: personContent, attributes: attributes)
                    let textHeight = attributedString.boundingRect(with: CGSize(width: contentWidth, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).height + 30
                    
                    // adding new page if we reach bottom of the current page
                    if currentY + textHeight > contentHeight + margin {
                        context.beginPage()
                        currentY = margin
                    }
                    attributedString.draw(in: CGRect(x: margin, y: currentY, width: contentWidth, height: textHeight))
                    currentY += textHeight
                }

Here is the complete code:

import SwiftUI
import PDFKit

struct ContentView: View {
    
    let people = [
        Person(name: "John Doe", number: "123-456-7890", address: "123 Main St"),
        Person(name: "Jane Smith", number: "987-654-3210", address: "456 Elm St"),
    ]
    
    var body: some View {
        Button("Generate PDF") {
            generatePDF()
        }
    }
    
    func generatePDF() {
        // adding a temporary pdf file
        let url = FileManager.default.temporaryDirectory.appendingPathComponent("output.pdf")

        // US Letter size pdf
        let pageWidth: CGFloat = 612
        let pageHeight: CGFloat = 792
        
        // variables for adding margin to content
        let margin: CGFloat = 50
        let contentWidth = pageWidth - 2 * margin
        let contentHeight = pageHeight - 2 * margin
        
        // variable for keep tracking height of content.
        var currentY: CGFloat = margin
        
        let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
        let renderer = UIGraphicsPDFRenderer(bounds: pageRect)

        // attributes for text size & fonts
        let attributes = [
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12)
        ]
        do {
            try renderer.writePDF(to: url) { context in
                //  start a pdf page
                context.beginPage()
                // adding dynamic text to pdf page
                for person in people {
                    let personContent = """
                             Name: \(person.name)
                             Number: \(person.number)
                             Address: \(person.address)
                             """
                    
                    let attributedString = NSAttributedString(string: personContent, attributes: attributes)
                    let textHeight = attributedString.boundingRect(with: CGSize(width: contentWidth, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).height + 30
                    
                    // adding new page if we reach bottom of the current page
                    if currentY + textHeight > contentHeight + margin {
                        context.beginPage()
                        currentY = margin
                    }
                    attributedString.draw(in: CGRect(x: margin, y: currentY, width: contentWidth, height: textHeight))
                    currentY += textHeight
                }
                
            }
            print("PDF saved at: \(url.path)")
        } catch {
            print("Error generating PDF: \(error)")
        }
    }
}
struct Person: Identifiable {
    let id = UUID()
    let name: String
    let number: String
    let address: String
}

The output will be:

You can apply various markdown formats to the text. This code will add pages automatically if required. You can try to increase data and see the result.

You can generate PDFs from SwiftUI views also. Where you can design PDF using SwiftUI and then create PDF. Here is the complete tutorial: Generate PDF from SwiftUI view using PDFKit – iOS

Leave a Reply