I've been working on adding a print feature to an app I developed for myself. The app consists of a parent / child relationship. So in the print function I want to first print the name of the parent, and then print all the child rows as a grid.
Sizing a page of US Letter this means I have roughly 612 x 792 units to draw in. Each child consists of a view, that set a frame of roughly 1/3 the width of the view 1/5 of the heigh. This should give me approximately 15 children on the page. However; my code seems to print the entire set of data (both parent and child) on top of each other at the top of the page.
@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: 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: 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
}
The issues is incorrect usage of .move. This needs to use .translatesBy
Using this means you have to remember were you were and then move the offset to a new location by .translatesBy
Final code is here
@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
}
}
}
pdfOutput.translateBy(x: -pageSize.width + 39.5, y: -153)
currentY -= 153
currentX = -pageSize.width + 39.5
print("Body - currentX = \(currentX), currentY = \(currentY)")
}
let renderBottom = ImageRenderer(
content:
Text("Page \((pageIndex + 1).formatted()) of \(numberOfPages.formatted())").frame(width: pageSize.width ,height: 20)
)
pdfOutput.translateBy(x: -pageSize.width + 39.5, 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
}