I have created a working variant, but it works only once.
When I try to call it again, it doesn't work. Seems like something with ViewController lifecycle.
I will be grateful for your help. Maybe we can beat it together.
extension View {
func exportPDF<Content: View>(@ViewBuilder content: @escaping () -> Content, completion: @escaping (Bool, URL?) -> ()) {
let documentDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let dateString = generateUniqueStringFromCurrentTime()
let outputFileURL = documentDirectory.appendingPathComponent("MyFile-\(dateString).pdf")
let rootVC = getRootController()
let pdfView = convertToScrollView {
content()
}
pdfView.tag = 123
let size = pdfView.contentSize
pdfView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
// Insert the View to RootView to render PDF and remove from it afterwards.
rootVC.view.insertSubview(pdfView, at: 0)
let renderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: size.width, height: size.height))
do {
try renderer.writePDF(to: outputFileURL, withActions: { context in
context.beginPage()
pdfView.layer.render(in: context.cgContext)
})
completion(true, outputFileURL)
} catch {
completion(false, nil)
print(error.localizedDescription)
}
rootVC.view.subviews.forEach { view in
if view.tag == 123 {
view.removeFromSuperview()
}
}
}
private func convertToScrollView<Content: View>(@ViewBuilder content: @escaping () -> Content) -> UIScrollView {
let scrollView = UIScrollView()
let hostController = UIHostingController(rootView: content()).view!
hostController.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostController.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
hostController.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
hostController.topAnchor.constraint(equalTo: scrollView.topAnchor),
hostController.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
hostController.widthAnchor.constraint(equalToConstant: screenBounds().width)
]
scrollView.addSubview(hostController)
scrollView.addConstraints(constraints)
scrollView.layoutIfNeeded()
return scrollView
}
private 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
}
private func screenBounds() -> CGRect {
return UIScreen.main.bounds
}
private func generateUniqueStringFromCurrentTime() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss-SSS" // Format: Year-Month-Day-Hour-Minute-Second-Millisecond
let dateString = dateFormatter.string(from: Date())
return dateString
}