1

Is it possible to get notified when Ajax has finished loading in UIWebView? In Android I was able to do it like this:

@SuppressLint("SetJavaScriptEnabled")
private void setUpWebView() {
    webview = view.findViewById(R.id.webView);
    webview.getSettings().setJavaScriptEnabled(true);
    webview.addJavascriptInterface(new JavaScriptInterface(), "HtmlViewer");
    webview.setWebViewClient(new LoginWebView());
    webview.loadUrl("EXAMPLE URL");
}

private class LoginWebView extends WebViewClient {
    @SuppressLint({"JavascriptInterface"})
    @Override
    public void onPageFinished(WebView view, String url) {
        view.loadUrl("javascript:(function(){" +
            "\n" + "    jQuery(document).ajaxComplete(function(event, request, settings){" +
            "\n" + "        var responseCode = JSON.parse(request.responseText).result.responseCode;" +
            "\n" + "        window.HtmlViewer.ajaxResponse(settings.url, responseCode);" +
            "\n" + "    });" +
            "\n" + "})();";);
    }
}

private class JavaScriptInterface {
    @JavascriptInterface
    public void ajaxResponse(String url, String responseCode) {
        // Gets notified when Ajax finished loading
    }
}

Now every time Ajax has finished loading, my ajaxResponse method is being called. Is it possible to achieve that also in UIWebView?

user3448282
  • 2,629
  • 3
  • 25
  • 47

3 Answers3

4

I finally found solution to this problem. There are many solution out there, but all of them uses jQuery which seems not working in WKWebView. Here is my solution in raw JavaScript.

Here is ViewController implementation that lets you be notified every AJAX request is completed in WKWebView:

import UIKit
import WebKit

class WebViewController: UIViewController {

    private var wkWebView: WKWebView!
    private let handler = "handler"

    override func viewDidLoad() {
        super.viewDidLoad()

        let config = WKWebViewConfiguration()
        let userScript = WKUserScript(source: getScript(), injectionTime: .atDocumentStart, forMainFrameOnly: false)
        config.userContentController.addUserScript(userScript)
        config.userContentController.add(self, name: handler)

        wkWebView = WKWebView(frame:  view.bounds, configuration: config)
        view.addSubview(wkWebView)

        if let url = URL(string: "YOUR AJAX WEBSITE") {
            wkWebView.load(URLRequest(url: url))
        } else {
            print("Wrong URL!")
        }
    }

    private func getScript() -> String {
        if let filepath = Bundle.main.path(forResource: "script", ofType: "js") {
            do {
                return try String(contentsOfFile: filepath)
            } catch {
                print(error)
            }
        } else {
            print("script.js not found!")
        }
        return ""
    }
}

extension WebViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let dict = message.body as? Dictionary<String, AnyObject>, let status = dict["status"] as? Int, let responseUrl = dict["responseURL"] as? String {
            print(status)
            print(responseUrl)
        }
    }
}

Pretty standard implementation. There is a WKWebView created programmatically. There is injected script that is loaded from script.js file.

And the most important part is script.js file:

var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
    this.addEventListener("load", function() {
        var message = {"status" : this.status, "responseURL" : this.responseURL}
        webkit.messageHandlers.handler.postMessage(message);
    });
    open.apply(this, arguments);
};

userContentController delegate method will be called every time there is AJAX request loaded. I'm passing there status and responseURL, because this was what I needed in my case, but you can also get more informations about request. Here is the list of all properties and methods available: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

My solution is inspired by this answer written by @John Culviner: https://stackoverflow.com/a/27363569/3448282

user3448282
  • 2,629
  • 3
  • 25
  • 47
0

You can use evaluateJavaScript:completionHandler:

webView.evaluateJavaScript("yourJSfuncsToRunAJAX") { response, error in
    //if error didn't occure do sth after js function finished
}
kkiermasz
  • 464
  • 2
  • 15
  • Thank you for the answer. The problem is that I need to use `UIWebView` which doesn't let to use `evaluateJavaScript` function with callback. I think I could try using `WKWebView` (it has bad cookie management system, that's why I need to use `UIWebView`). The problem is I don't know how should JS script look like in iOS version. On Android I'm calling `window.HtmlViewer.ajaxResponse(settings.url, responseCode);` that let's me deliver callback information to my function. How to achieve this on iOS? – user3448282 Aug 30 '18 at 08:24
  • @user3448282 Apple recommend to use WKWebView, so give it a try ;) JS is pretty similiar on every browser so if it works on chromium-based browser it should work on safari base too. – kkiermasz Aug 30 '18 at 08:29
0

Unfortunately UIWebView is deprecated from iOS 8, so you should switch to WKWebView. From apple: "In apps that run in iOS 8 and later, use the WKWebView class instead of using UIWebView."

To get notified if ajax finished you can use the accepted response from here. I used the same principle for bridging between javascript and swift.

Hope it helps.

Cristi Ghinea
  • 474
  • 1
  • 7
  • 20