Search code examples
swiftuikitwebkitwkwebviewmac-catalyst

WKWebView not calling navigation delegate methods


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)
        }

Solution

  • 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
        //...
    }