7

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

}

}

2 Answers 2

26

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
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Don't you think this add extra file in code and might not be preferred when project costing is based on LoC . Anyways +1!
Par great answer works, Helped me get logged in to web sire I needed. Question, then at this site another Webpage appears after login, I need to run a javascript there, to enter a last name and return? ideas where /how to do that? you can email me be best maybe. thank you Mike
Hi @Mike - I'm afraid your question is too complicated for an answer in comments, and isn't really on-topic for this particular question and answer. A great deal depends on the web page that appears after login and how you can interact with it. I recommend you start a new question here at stackoverflow and show people what you've come up with so far. Hopefully you'll get many helpful answers!
1

I know it has been a while since this was answered and I found that this didn't really work for me. I used the below instead.

I could not get the evaluateJavaScript to work until i added the setTimeout() function. For what it is worth here is my code below.

webView.evaluateJavaScript("setTimeout(function(){ 
// Do something here
},10);") { (result1, error) in
            if error == nil {
                print(result1 ?? "")
            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.