TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SwiftUI to PDF Layout issues

Forums > SwiftUI

When translating from SwiftUI space to PDF Space, I am struggling to get my output to work. I have a simple PrintView element that is .framed as .frame(width:143, height: 134) When I run the following code, I end up with all the PrintView's overlaying each other at the top left of the PDF page. I've tried manipulating my x/y cooridnates with both move(to: and .translateBy but neither seem to do anything different.

@MainActor func render(viewsPerPage: Int) -> URL {
        let eventsArray: [Event] = events.map { $0 }
        let url = URL.documentsDirectory.appending(path: "\(recipient.wrappedFirstName)-\(recipient.wrappedLastName)-cards.pdf")
        var pageSize = CGRect(x: 0, y: 0, width: 612, height: 792)

        guard let pdfOutput = CGContext(url as CFURL, mediaBox: &pageSize, nil) else {
            return url
        }

        let numberOfPages = Int((events.count + viewsPerPage - 1) / viewsPerPage)   // Round to number of pages
        let xColumn = [0.0, 153.0, 306.0, 459.0]
        let yRow = [548.0, 413.0, 269.0, 115.0, 0.0]
        let viewsPerRow = 4
        let rowsPerPage = 5
        let spacing = 10.0

        // Note the page should be laid out as follows
        // Header Start on Row 792 to Row 692 (100 Pixels)
        // Body is a Grid of 143w X 134h PrintViews
        // Footer Starts on Row 0 to Row 20 (20 Pixels)

        for pageIndex in 0..<numberOfPages {
            pdfOutput.beginPDFPage(nil)

//            let rendererTop = ImageRenderer(content: AddressView(recipient: recipient))
            let rendererTop = ImageRenderer(content: Color.red.frame(width: pageSize.width, height: 90))
            rendererTop.render { size, renderTop in
                // Go to Bottom Left of Page
                pdfOutput.move(to: CGPoint(x: 0.0, y: 0.0))
                // Translate to top Left with size of AddressView and Padding
                pdfOutput.translateBy(x: 0.0, y: pageSize.height - size.height - spacing)
                renderTop(pdfOutput)
                print("\n\nStarting page = \(pageIndex)")
            }

            let startIndex = pageIndex * viewsPerPage
            let endIndex = min(startIndex + viewsPerPage, eventsArray.count)

            for row in 0..<rowsPerPage {
                let yTranslation = yRow[row]

                for col in 0..<viewsPerRow {
                    let index = startIndex + row * viewsPerRow + col
                    if index < endIndex, let event = eventsArray[safe: index] {
                        let xTranslation = xColumn[col] // CGFloat(col) * (viewWidth + spacing)
//                        let renderBody = ImageRenderer(content: PrintView(event: event))
                        let renderBody = ImageRenderer(content: Text("Event[\(index)] x=\(xColumn[col])/y=\(yRow[row] - 148)").frame(width: 134, height: 148).background(Color.blue))

                        renderBody.render { size, renderBody in
                            pdfOutput.move(to: CGPoint(x: xColumn[col], y: yRow[row] - size.height))
                            renderBody(pdfOutput)
                            print("Event \(index) Position x= \(xColumn[col]) / y = \(yRow[row] - 148)")
                        }
                    }
                }
            }

            let renderBottom = ImageRenderer(content: Text("Page \((pageIndex + 1).formatted()) of \(numberOfPages.formatted())").frame(width: pageSize.width, height: 20).background(Color.yellow))
            pdfOutput.move(to: CGPoint(x: pageSize.width / 2 , y: 0))

            renderBottom.render { size, renderBottom in
                renderBottom(pdfOutput)
                print("\nEnding page = \(pageIndex)")
            }

            pdfOutput.endPDFPage()
        }

        pdfOutput.closePDF()
        return url
    }

To make this more expressive, I am now rendering big boxes for each item... When I have only 1 event, The Event overlaps the renderHeader on the top left, but shows x= 0.0 and y=400.

It appears that the pdfOutput.move(to: CGPoint(x: xColumn[col], y: yRow[row] - size.height)) does actually move the position in the rendering space at all.

3      

The basic behavior is like the first rendering working fine, but after that it ignores all the .move(to: CGPoint) and additional renderings. Should I not be rendering the whole image until the end?

3      

OK, I resolved this by "correctly" using translateBy.. evidently you have to keep track of every position and shift to the next position. .move does not work. Having said that, I still can't figureout the correct positioning for my bottom of page footer. Oh well, may have to just map out a pixel grid for the whole page.

Here's the code as it stands

    @MainActor func render(viewsPerPage: Int) -> URL {
        let eventsArray: [Event] = events.map { $0 }
        let url = URL.documentsDirectory.appending(path: "\(recipient.wrappedFirstName)-\(recipient.wrappedLastName)-cards.pdf")
        var pageSize = CGRect(x: 0, y: 0, width: 612, height: 792)

        guard let pdfOutput = CGContext(url as CFURL, mediaBox: &pageSize, nil) else {
            return url
        }

        let numberOfPages = Int((events.count + viewsPerPage - 1) / viewsPerPage)   // Round to number of pages
        let viewsPerRow = 4
        let rowsPerPage = 4
        let spacing = 10.0

        // Note the page should be laid out as follows
        // Header Start on Row 792 to Row 692 (100 Pixels)
        // Body is a Grid of 143w X 134h PrintViews
        // Footer Starts on Row 0 to Row 20 (20 Pixels)

        for pageIndex in 0..<numberOfPages {
            var currentX : Double = 0
            var currentY : Double = 0

            pdfOutput.beginPDFPage(nil)
            let rendererTop = ImageRenderer(content: AddressView(recipient: recipient))
            rendererTop.render { size, renderTop in
                // Go to Bottom Left of Page
                pdfOutput.move(to: CGPoint(x: 0.0, y: 0.0))
                // Translate to top Left with size of AddressView and Padding
                pdfOutput.translateBy(x: 0.0, y: pageSize.height - size.height - spacing)
                currentY += pageSize.height - size.height - spacing
                renderTop(pdfOutput)
                print("\n\nStarting page = \(pageIndex)")
            }
            print("Header - currentX = \(currentX), currentY = \(currentY)")

            let startIndex = pageIndex * viewsPerPage
            let endIndex = min(startIndex + viewsPerPage, eventsArray.count)
            pdfOutput.translateBy(x: spacing / 2, y: -160)

            for row in 0..<rowsPerPage {
                for col in 0..<viewsPerRow {
                    let index = startIndex + row * viewsPerRow + col
                    if index < endIndex, let event = eventsArray[safe: index] {
                        let renderBody = ImageRenderer(content: PrintView(event: event))
                        renderBody.render { size, renderBody in
                            renderBody(pdfOutput)
                            pdfOutput.translateBy(x: 144, y: 0) // (to: CGPoint(x: xColumn[col], y: yRow[row] - size.height))
                            currentX += size.width
                        }
                        print("Body - currentX = \(currentX), currentY = \(currentY)")
                    }
                }
                pdfOutput.translateBy(x: -pageSize.width + 39.5, y: -153)
                currentY -= 153
                currentX = -pageSize.width + 39.5
            }

            let renderBottom = ImageRenderer(
                content:
                    Text("Page \((pageIndex + 1).formatted()) of \(numberOfPages.formatted())").frame(width: pageSize.width - (spacing * 2) ,height: 20)
            )
            currentY -= 20 + (spacing * 2)
            currentX = -pageSize.width + 39.5
            pdfOutput.translateBy(x: currentX, y: -currentY)
            print("Footer - currentX = \(currentX), currentY = \(currentY)")
            renderBottom.render { size, renderBottom in
                renderBottom(pdfOutput)
                print("\nEnding page = \(pageIndex), size.width =\(size.width)  , size.height=\(size.height)")
            }
            pdfOutput.endPDFPage()
        }
        pdfOutput.closePDF()
        return url
    }

3      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.