2

I use WKWebView and I want to be notified when website is fully loaded. The webView:didFinishNavigation method of WKNavigationDelegate is fired when document.readyState is either interactive or complete and I want to be sure that site was completely loaded. I came up with the solution which uses JavaScript injection. Here is my MWE:

import UIKit
import WebKit

class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate {

    var webView: WKWebView!

    @IBOutlet weak var loadLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        let contentController = WKUserContentController()

        let scriptPath = NSBundle.mainBundle().pathForResource("script", ofType: "js")!
        let scriptString = try! String(contentsOfFile: scriptPath)
        let script = WKUserScript(source: scriptString, injectionTime: .AtDocumentStart, forMainFrameOnly: true)

        contentController.addUserScript(script)
        contentController.addScriptMessageHandler(self, name: "readyHandler")

        let configuration = WKWebViewConfiguration()
        configuration.userContentController = contentController

        webView = WKWebView(frame: CGRect.zero, configuration: configuration)
        webView.navigationDelegate = self
        loadLabel.text = nil
    }

    @IBAction func loadWebsite() {
        webView.loadRequest(NSURLRequest(URL: NSURL(string: "http://stackoverflow.com")!))
        loadLabel.text = "Loading..."
    }

    func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
        print("message received")
        loadLabel.text = "Complete"
    }
}

And this is the content of script.js file:

document.onreadystatechange = function () {
    if(document.readyState === "complete"){
        webkit.messageHandlers.readyHandler.postMessage("");
    }
}

userContentController:didReceiveScriptMessage method is always called on iOS Simulator, but on the actual device (iPhone 6 in my case) it isn't called most of the times. Any idea what can be wrong about it or what's the other way of checking if website is completely loaded?

Sebastian Osiński
  • 2,894
  • 3
  • 22
  • 34
  • Does `.AtDocumentEnd` work? – soflare Feb 26 '16 at 18:11
  • @soflare On simulator, yes, on device, no. What's more interesting, I tested it on another device (iPhone 5 with iOS 8.4.1) and it works fine. So it looks like a problem with iOS 9 itself. – Sebastian Osiński Feb 26 '16 at 18:15
  • Make sure: 1. the script is injected; 2. the script is executed; 3. the message is delivered. Which step is broken? Try to attach webview with Safari and examine in JS console. – soflare Feb 26 '16 at 18:30

1 Answers1

0

For some reason you need to add the webView to a visible view for this to work on the device. If you don't want the webView to be visible, add it and then set the hidden property to true.

For the code example above:

func viewDidLoad(){
   ...
   webView.hidden = true
   view.addSubview(webView)
}
xoogler
  • 211
  • 5
  • 12
  • Unfortunately it isn't possible to add this webView to some visible view controller - I need this functionality for framework for exporting webpages as pdf. I'll try to create view controller with webview which I won't actually show to user. I guess this trick - https://www.natashatherobot.com/ios-testing-view-controllers-swift/ - might work here. – Sebastian Osiński Jul 25 '16 at 13:19
  • I faced the same issue in my project where all requireJS calls were hanging. As xoogler mentioned, the root cause is that the web view has to be part of the view hierarchy before the web site/ page is loaded. My project is a framework, so it does not own the enclosing view and cannot add the web view to the hierarchy. So I have to modify my API to inform the client to add the web view to the hierarchy before I load the web content. – Ann Catherine Jose Sep 02 '16 at 07:37