I'm trying to export an html file as a pdf using the answer here which uses a WKWebView
to load an html string and then draw a PDF from that. I also need it to work on Catalyst, which is why I'm using this approach.
What's happening is that the WKWebView
is never calling it's navigationDelegate
methods. I've put a bunch of print()
statements in a bunch of delegate methods but the only output I get is:
::::: SET WEBVIEW
Loaded
I can't figure out what is making the WKWebView
not call it's delegate methods. I've also tried loading a URL like https://google.com and it fails the same way. I've even observed loading progress from the webview, and it is loading the content, just not calling the delegate.
I'd appreciate any help, I need this to work.
class ReportsRenderer: NSObject {
var webView: WKWebView? = nil {
didSet {
print("::::: SET WEBVIEW")
}
}
var completion: PDFCompletion!
typealias PDFCompletion = (Result<NSData, Error>) -> Void
func exportPDF(html: String, completion: @escaping PDFCompletion) throws {
self.completion = completion
webView = WKWebView()
webView?.navigationDelegate = self
let baseURL = URL(fileURLWithPath: NSTemporaryDirectory())
webView?.loadHTMLString(html, baseURL: baseURL)
print("Loaded")
}
func createPDF(_ formatter: UIViewPrintFormatter) {
print("Creating PDF")
let printPageRenderer = UIPrintPageRenderer()
printPageRenderer.addPrintFormatter(formatter, startingAtPageAt: 0)
let paperRect = CGRect(x: 0, y: 0, width: 595.2, height: 841.8)
let padding: CGFloat = 20
let printableRect = paperRect.insetBy(dx: padding, dy: padding)
printPageRenderer.setValue(printableRect, forKey: "printableRect")
printPageRenderer.setValue(paperRect, forKey: "paperRect")
printPageRenderer.footerHeight = 70
printPageRenderer.headerHeight = 20
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, .zero, nil)
for i in 0..<printPageRenderer.numberOfPages {
UIGraphicsBeginPDFPage();
printPageRenderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}
UIGraphicsEndPDFContext();
self.completion?(.success(pdfData))
}
}
extension ReportsRenderer: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("::::: WEBVIEW NAVIGATED")
let viewPrintFormatter = webView.viewPrintFormatter()
createPDF(viewPrintFormatter)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("::::: WEBVIEW ERROR")
print(error.localizedDescription)
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("::::: WEBVIEW DIDCOMMIT NAVIGATION")
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("::::: WEBVIEW didStartProvisionalNavigation")
}
}
I'm calling it in my ViewController like:
do {
try ReportsRenderer().exportPDF(html: string) { (result) in
switch result {
case .success(let pdfData):
// ---- This is never called -----
print("Made the pdf data!")
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let pdfFilename = paths[0].appendingPathComponent("\(UUID().uuidString).pdf")
pdfData.write(to: pdfFilename, atomically: true)
case .failure(let error):
// ---- This is also never called -----
print("::::: Error")
print(error.localizedDescription)
}
}
} catch {
// There isn't an error here either
print("::::: Error")
print(error)
}
You need to create an instance of ReportsRenderer
in ViewController
to make it work. When you create an instance in ViewController
it lives as long as the ViewController
lives. In your case, it was getting deinitialized immediately.
class ViewController: UIViewController {
let reportsRenderer = ReportsRenderer()
// then...
do {
try reportsRenderer.exportPDF(html: string) { (result) in
//...
}