Search code examples
iosswiftwkwebview

WKWebView evaluateJavaScript not returning html


I am trying to parse the html returned from a WKWebView load() with evaluateJavaScript but it never prints anything. Am I doing this right? Any other ways? didFinish does print.

import UIKit
import WebKit

class MyWebViewController: UIViewController, WKNavigationDelegate {

var webView: WKWebView!

override func viewDidLoad() {
    super.viewDidLoad()

    webView = WKWebView(frame:  self.view.frame)
    webView.navigationDelegate = self

    let url = NSURL (string: "https://google.com");
    let request = NSURLRequest(url: url! as URL)
    webView.load(request as URLRequest)

    self.view.addSubview(webView)

    self.view.sendSubview(toBack: webView)

}


func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

    webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { (html: AnyObject?, error: NSError?) in
        print(html!)
        } as? (Any?, Error?) -> Void)

    print("didFinish")

}

}


Solution

  • Using evaluateJavaScript with a WKWebView is a bit tricky.

    Since I think this answer would be useful to many people, rather than address your specific question with a short code snippet and a comment that you need to implement WKScriptMessageHandler, I'm going to post a full, complete example that you can use to see how everything works together.

    To use this, create a "Single View Application" iOS project in Xcode and paste this over the default ViewController.swift file.

    //
    //  WebViewController.swift
    //  WKWebViewExample
    //
    //  Created by par on 4/2/17.
    //  Copyright © 2017 par. All rights reserved.  MIT License.
    //
    
    import UIKit
    import WebKit
    
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let webViewController = WebViewController()
    
            // install the WebViewController as a child view controller
            addChildViewController(webViewController)
    
            let webViewControllerView = webViewController.view!
    
            view.addSubview(webViewControllerView)
    
            webViewControllerView.translatesAutoresizingMaskIntoConstraints = false
            webViewControllerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
            webViewControllerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
            webViewControllerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
            webViewControllerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
    
            webViewController.didMove(toParentViewController: self)
        }
    }
    
    class WebViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
        private var webView: WKWebView!
        private var webViewContentIsLoaded = false
    
        init() {
            super.init(nibName: nil, bundle: nil)
    
            self.webView = {
                let contentController = WKUserContentController()
    
                contentController.add(self, name: "WebViewControllerMessageHandler")
    
                let configuration = WKWebViewConfiguration()
                configuration.userContentController = contentController
    
                let webView = WKWebView(frame: .zero, configuration: configuration)
                webView.scrollView.bounces = false
                webView.navigationDelegate = self
    
                return webView
            }()
        }
    
        required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.addSubview(webView)
    
            webView.translatesAutoresizingMaskIntoConstraints = false
            webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
            webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
            webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            if !webViewContentIsLoaded {
                let url = URL(string: "https://stackoverflow.com")!
                let request = URLRequest(url: url)
    
                webView.load(request)
    
                webViewContentIsLoaded = true
            }
        }
    
        private func evaluateJavascript(_ javascript: String, sourceURL: String? = nil, completion: ((_ error: String?) -> Void)? = nil) {
            var javascript = javascript
    
            // Adding a sourceURL comment makes the javascript source visible when debugging the simulator via Safari in Mac OS
            if let sourceURL = sourceURL {
                javascript = "//# sourceURL=\(sourceURL).js\n" + javascript
            }
    
            webView.evaluateJavaScript(javascript) { _, error in
                completion?(error?.localizedDescription)
            }
        }
    
        // MARK: - WKNavigationDelegate
    
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            // This must be valid javascript!  Critically don't forget to terminate statements with either a newline or semicolon! 
            let javascript =
                "var outerHTML = document.documentElement.outerHTML.toString()\n" +
                "var message = {\"type\": \"outerHTML\", \"outerHTML\": outerHTML }\n" +
                "window.webkit.messageHandlers.WebViewControllerMessageHandler.postMessage(message)\n"
    
            evaluateJavascript(javascript, sourceURL: "getOuterHMTL")
        }
    
        // MARK: - WKScriptMessageHandler
    
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            guard let body = message.body as? [String: Any] else {
                print("could not convert message body to dictionary: \(message.body)")
                return
            }
    
            guard let type = body["type"] as? String else {
                print("could not convert body[\"type\"] to string: \(body)")
                return
            }
    
            switch type {
            case "outerHTML":
                guard let outerHTML = body["outerHTML"] as? String else {
                    print("could not convert body[\"outerHTML\"] to string: \(body)")
                    return
                }
                print("outerHTML is \(outerHTML)")
            default:
                print("unknown message type \(type)")
                return
            }
        }
    }