I have been trying to find a way to export a View in SWIFTUI in A4 size pages PDF. The view cannot fit in one single page. So far I have managed to edit (with the help of ChatGPT) the code found in PDF Creator GitHub (See below)
But although I get multiple page PDF as a result only the first page is populated, the rest are just blank.
Does anyone faced something similar before, if yes how did you manage to export to PDF in A4 pages...
Thanks for any tips and help!
extension View{
func sharePDF<Content: View> (@ViewBuilder content: @escaping () -> Content, fileName: String) {
exportPDF(content: content, completion: { status , url in
if let url = url, status {
ShareSheet.instance.share(items: [url])
} else {
print("⚠️ Failed to make PDF")
}
}, fileName: fileName)
}
// MARK: Extracting View's Height and width with the Help of Hosting Controller and ScrollView
fileprivate func convertToScrollView<Content: View>(@ViewBuilder content: @escaping ()->Content)->UIScrollView{
let scrollView = UIScrollView()
// MARK: Converting SwiftUI View to UIKit View
let hostingController = UIHostingController(rootView: content()).view!
hostingController.translatesAutoresizingMaskIntoConstraints = false
// MARK: Constraints
let constraints = [
hostingController.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
hostingController.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
hostingController.topAnchor.constraint(equalTo: scrollView.topAnchor),
hostingController.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
// Width Anchor
hostingController.widthAnchor.constraint(equalToConstant: screenBounds().width)
]
scrollView.addSubview(hostingController)
scrollView.addConstraints(constraints)
scrollView.layoutIfNeeded()
return scrollView
}
// MARK: Export to PDF
// MARK: Completion Handler will Send Status and URL
fileprivate func exportPDF<Content: View>(@ViewBuilder content: @escaping () -> Content, completion: @escaping (Bool, URL?) -> (), fileName: String) {
// MARK: Temp URL
let documentDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
// MARK: To Generate New File whenever it's generated
let outputFileURL = documentDirectory.appendingPathComponent("\(fileName)\(UUID().uuidString).pdf")
// MARK: PDF View
let scrollView = convertToScrollView {
content()
}
scrollView.tag = 1009
scrollView.frame = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4 size in points (72 points per inch)
let pageSize = scrollView.frame.size
let contentSize = scrollView.contentSize
let pageCount = Int(ceil(contentSize.height / pageSize.height))
// Create PDF Context
UIGraphicsBeginPDFContextToFile(outputFileURL.path, .zero, nil)
// CHATGPT:
for index in 0..<pageCount {
// Begin new PDF page
UIGraphicsBeginPDFPageWithInfo(CGRect(origin: .zero, size: pageSize), nil)
// Calculate the visible frame for each page
let visibleFrame = CGRect(x: 0, y: -pageSize.height * CGFloat(index), width: pageSize.width, height: pageSize.height)
// Capture the screenshot of the visible content synchronously
scrollView.clipToRect(visibleFrame) {
// Take a screenshot of the visible content
let screenshot = scrollView.takeScreenshot()
// Draw the screenshot into the PDF context
screenshot.draw(at: .zero)
}
}
completion(true, outputFileURL)
// End PDF Context
UIGraphicsEndPDFContext()
completion(true, outputFileURL)
// Removing the added View
getRootController().view.subviews.forEach { view in
if view.tag == 1009 {
print("Removed")
view.removeFromSuperview()
}
}
}
fileprivate func screenBounds()->CGRect{
return UIScreen.main.bounds
}
fileprivate func getRootController()->UIViewController{
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else{
return .init()
}
guard let root = screen.windows.first?.rootViewController else{
return .init()
}
return root
}
fileprivate func getSafeArea()->UIEdgeInsets{
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else{
return .zero
}
guard let safeArea = screen.windows.first?.safeAreaInsets else{
return .zero
}
return safeArea
}
}
extension UIView {
func takeScreenshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
drawHierarchy(in: bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image ?? UIImage()
}
func clipToRect(_ rect: CGRect, perform: () -> Void) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
context.clip(to: rect)
perform()
context.restoreGState()
}
}