Rendering Multi-Page PDF

I have the following code for generating a one page PDF:

@MainActor func render() -> URL {
    let renderer = ImageRenderer(content: pdfView))
    let url = URL.documentsDirectory.appending(path: "output.pdf")
    
    renderer.render { size, context in
        var document = CGRect(x: 0, y: 0, width: 2550, height: 3300)
        guard let pdf = CGContext(url as CFURL, mediaBox: &document, nil) else {
            return
        }
        
        pdf.beginPDFPage(nil)
        
        context(pdf)
        pdf.endPDFPage()
        pdf.closePDF()
    }
    
    return url
}

I'm trying to write code to create a multi-page PDF if there is multiple ImageRenderers. I tried something shown below but I'm not sure how to properly implement.

@MainActor func render() -> URL {
    let renderer = ImageRenderer(content: pdfView)
    let url = URL.documentsDirectory.appending(path: "output.pdf")

    for image in renderer {
        image.render { size, context in
            var page = generatePage(image: image)
        }
    }
    return url
}

func generatePage(image: ImageRenderer<<#Content: View#>>) -> CGContext {
    var view = CGRect(x: 0, y: 0, width: 2550, height: 3300)
    guard let pdf = CGContext(url as CFURL, mediaBox: &view, nil) else {
        return
    }
    
    pdf.beginPDFPage(nil)
    
    context(pdf)
    
    pdf.endPDFPage()
    pdf.closePDF()
    
    return pdf
}

Any guidance would be greatly appreciated. Thank you.

Answered by DTS Engineer in 825120022

In the example you provided you're calling "guard let pdf = CGContext(url as CFURL, mediaBox: &view, nil)" inside of your generatePage every time you want to add a page to the document. I was expecting to see you calling that once at the beginning and then using the same context over and over again when adding pages using the beginPDFPage(), endPDFPage() pair. Then, after you have added all of the pages, you'd call closePDF() on that context. You're pretty close. I think all you need to do is just a little restructuring of the calls you are making.

In the example you provided you're calling "guard let pdf = CGContext(url as CFURL, mediaBox: &view, nil)" inside of your generatePage every time you want to add a page to the document. I was expecting to see you calling that once at the beginning and then using the same context over and over again when adding pages using the beginPDFPage(), endPDFPage() pair. Then, after you have added all of the pages, you'd call closePDF() on that context. You're pretty close. I think all you need to do is just a little restructuring of the calls you are making.

Thank you, I now have this:

@MainActor func render() -> URL {
    let renderer = [ImageRenderer(content: pdfView())]
    let url = URL.documentsDirectory.appending(path: "output.pdf")
    
    var view = CGRect(x: 0, y: 0, width: 2550, height: 3300)
    guard let pdf = CGContext(url as CFURL, mediaBox: &view, nil) else {
        return nil // error
    }
    
    for image in renderer {
        image.render { size, context in
            pdf.beginPDFPage(nil)
            
            context(pdf)
            pdf.endPDFPage()
        }
    }
    
    pdf.closePDF()
}

I have two follow up questions:

I'm getting an error shown above for returning a nil value but I'm not sure how to otherwise handle the else statement.

I have a function pdfView that returns the view to be rendered. The syntax to return is -> some View { . Is it possible to return an array of views?

Just following up. Thank you.

Rendering Multi-Page PDF
 
 
Q