I'm building an identity app which is stored all locally within the app on device.
Part of the app is the creation of IdentityCard
for each user - with the standard things, name, image, position.
The size of the card is a standard credit card size: let cardSize: CGSize = .init(width: 153.8, height: 242.1)
in the vertical position.
I want to create a "generate PDF" button so that each user's ID card will be printed into a PDF. I have the ability to select the paper size (A4, Letter, or custom - where they can select their own width, height, and unit).
At the moment I have this to generate the PDF, but I am running into a few issues:
- It doesn't output anything in a
.sheet
- It is slow to run
- It crashes when testing on larger sets of users
import SwiftUI
import PDFKit
class PDFDataManager {
static let shared = PDFDataManager()
private init() {}
struct Item<Content : View> {
let views: [Content]
let width: CGFloat
let height: CGFloat
}
func generate<Content : View>(
from item: Item<Content>,
paper: PageSize = .a4,
margin: Double = 36,
bleed: Double = 10
) -> PDFDocument {
let usablePageSize: CGSize = .init(
width: paper.size.width - margin - (2 * bleed),
height: paper.size.height - margin - (2 * bleed)
)
let itemSize: CGSize = .init(
width: item.width,
height: item.height
)
let maxRows = Int(floor(usablePageSize.height / itemSize.height))
let maxCols = Int(floor(usablePageSize.width / itemSize.width))
let pdfDocument = PDFDocument()
let pageSize = CGRect(
x: 0, y: 0,
width: paper.size.width,
height: paper.size.height
)
var currentItem = 0
var currentRow = 0
var currentCol = 0
DispatchQueue.global(qos: .userInitiated).async {
while currentItem < item.views.count {
let pdfPage = PDFPage()
pdfPage.setBounds(pageSize, for: .trimBox)
let pdfView = PDFView(frame: pageSize)
pdfView.autoScales = true
pdfView.displayDirection = .vertical
pdfView.displayMode = .singlePageContinuous
pdfView.document = pdfDocument
pdfView.pageBreakMargins = .init(top: 0, left: 0, bottom: 0, right: 0)
while currentItem < item.views.count && currentRow <= maxRows {
autoreleasepool {
DispatchQueue.main.async {
if currentItem < item.views.count {
let itemView = UIHostingController(rootView: item.views[currentItem])
let x = CGFloat(currentCol) * (itemSize.width + (bleed * 2))
let y = CGFloat(currentRow) * (itemSize.height + (bleed * 2))
let itemRect = CGRect(
x: x, y: y,
width: itemSize.width,
height: itemSize.height
)
itemView.view.frame = itemRect
pdfView.addSubview(itemView.view)
currentCol += 1
if currentCol >= maxCols {
currentCol = 0
currentRow += 1
}
currentItem += 1
}
}
}
}
DispatchQueue.main.async {
pdfDocument.insert(pdfPage, at: pdfDocument.pageCount)
}
}
}
return pdfDocument
}
}
What I've aimed to do / What my aim is to do:
- Pass in all the items through a temp struct
- Have a page margin, and an item bleed spacer
- Calculate the max items in a row
- Calculate the max items in a column
- Loop over all the items and place them onto the page
Once I would have that working I would be able to share the PDF, print the PDF, or export the PDF. However, at the moment I cant do any of that and I'm kind of lost of where to go from here.
I am targeting iOS/iPadOS 15 and above.