JDev
JDev

Reputation: 5558

Detect button click (by class name) in WKWebView with JavaScript.evaluate

I've been attempting to add a listener for when the user click this button element in a WKWebView:

<div class="web-navigation__auth" data-test-web-chrome-auth="">
    <button class="web-navigation__auth-button web-navigation__auth-button--sign-in button button-reset button--auth">
        <svg ... ></path></g></svg>
        Sign In
    </button>

    <div class="web-chrome-auth-container__spinner web-chrome-auth-container__spinner--hidden"></div>
</div>

I've attempted to observe using numerous different JavaScript evaluations, and have tried the code in different places (ie. viewDidLoad, webView(didFinish), webView(didStartProvisionalNavigation), etc):

let js1 = "document.addEventListener('click', function(e) { e = e || window.event; if(e.target.getAttribute('class') == 'web-navigation__auth-button') { console.log(e.target.value); } });"
let js2 = "var elements = document.getElementsByClassName('web-navigation__auth'); var myFunction = function() { var attribute = this.getAttribute('web-navigation__auth-button--sign-in'); alert(attribute); }; for (var i = 0; i < elements.length; i++) { elements[i].addEventListener('click', myFunction, false); }"
let js3 = "document.addEventListener('click', getElementsByClassName('web-navigation__auth-button').onclick();"

webView.evaluateJavaScript(js) { (key, err) in
    if let err = err {
        print(err.localizedDescription)
    } else {
        print("JS: You tapped the Sign In button!")
    }
}

The output to console only seems to appear whenever the webView.evaluateJavaScript is called, not when button is pressed. I have not gotten any code to trigger when the button is pressed, aside from the note below regarding js3. This the the output to console for each JS call in webView(didFinish):

js1: A JavaScript exception occurred     // on webView.didFinish
     JS: You tapped the Sign In button!  // on webView.didFinish

js2: A JavaScript exception occurred     // on webView.didFinish
     JS: You tapped the Sign In button!  // on webView.didFinish

js3: A JavaScript exception occurred     // on webView.didFinish
     A JavaScript exception occurred     // on Sign In button click (fired again)

One thing to note about js3 is that, it will fire the error message on webView.didFinish, but it will also fire the error if the button is pressed.

So theoretically, I could create a flag to check for the clicking of that button; however, I would prefer something more concrete than this hacky workaround:

// Error output to console on every new page load AND "Sign In" button click
var pageLoad = false    // Page has not yet loaded, false by default

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    pageLoad = true     // Page has loaded, unless new URL detected, next error = button click!
    if webView.urlHasChanged() { pageLoad = false }
    
    let js3 = "document.getElementsByClassName('web-navigation__auth-button').onclick();"
    webView?.evaluateJavaScript(js3) { (key, err) in
    if let err = err {  
        if pageLoad {
            // Error message && pageLoad = "Sign in" button pressed: CODE HERE
        }
    }
    if pageLoad && url.hasNotChanged() { pageLoad = false }
}

Upvotes: 3

Views: 7429

Answers (2)

congnd
congnd

Reputation: 1274

You can use the WKScriptMessageHandler without the need of changing your website source code.

Step1. Instantiate your WKWebView instance with a registered message handler.

let contentController = WKUserContentController()

contentController.add(self, name: "MessageHandler")

let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController

let webview = WKWebView(frame: .zero, configuration: configuration)

Step 2. Inject a JS code that listens to the click event and sends a message to the message handler registered above whenever a click event arrived. This code MUST be executed AFTER your page is loaded.

let jscode = """
buttonSelector.addEventListener('click', function(event) {
  window.webkit.messageHandlers.MessageHandler.postMessage("clicked")
})
"""
webview.evaluateJavaScript(jscode) { _, _ in }

Step 3. Handle incoming messages in your app

extension YourClass: WKScriptMessageHandler {
  public func userContentController(
    _ userContentController: WKUserContentController,
    didReceive message: WKScriptMessage
  ) {
    print(message) 
  }
}

Upvotes: 1

Shashank Mishra
Shashank Mishra

Reputation: 1079

You can do it by adding a script message handler to userContentController and listening to

 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){}

Below is the full code

class ViewController: UIViewController,WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {

    var webView: WKWebView!
    
    override func loadView() {
        webView = WKWebView()
        webView.navigationDelegate = self
        view = webView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //Fetch html
        let url = URL(fileURLWithPath: Bundle.main.path(forResource: "example", ofType: "html") ?? "")
        let config: WKWebViewConfiguration = WKWebViewConfiguration()
        // Adding a script message handler
        config.userContentController.add(self, name: "test")
        webView = WKWebView(frame: self.view.frame, configuration: config)
        webView?.navigationDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = true
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.addSubview(self.webView)
        //Load html page
        self.webView?.loadFileURL(url, allowingReadAccessTo: Bundle.main.bundleURL)
    }

    // Delegate method on click action
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "test", let messageBody = message.body as? String {
            print(messageBody)
        }
    }

}

You can use sample HTML with javascript as below

<div id="test" style="height: 40px; width: 100px; background-color: powderblue;">Hello</div>
<script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="js/platform.js"></script>

<script type="text/javascript">
document.getElementById("test").addEventListener("click", function test() {
    webkit.messageHandlers.test.postMessage("TEXT");
});
</script>

Let me know if you are looking for the same.

Upvotes: 2

Related Questions