Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject JavaScript callback to detect onclick event, using iOS WKWebView?

I'm using a WKWebView to show a website which has some HTML that includes three buttons. I want to run some Swift code in the native app when a specific button is clicked.

About the HTML

The three buttons look like this:

<input type="button" value="Edit Info" class="button" onclick="javascript:GotoURL(1)">
<input type="button" value="Start Over" class="button" onclick="javascript:GotoURL(2)">
<input type="button" value="Submit" class="button" onclick="javascript:GotoURL(3)">

The GotoURL function they call looks like this:

function GotoURL(site)
{
    if (site == '1')
        document.myWebForm.action = 'Controller?op=editinfo';
    if (site == '2')
        document.myWebForm.action = 'Controller?op=reset';
    if (site == '3')
        document.myWebForm.action = 'Controller?op=csrupdate';
    document.myWebForm.submit();
}

Current WKWebView Implementation

When I click any of the buttons in the webview, this function is called on my WKNavigationDelegate:

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    // ...?
}

But of course, navigation is opaque and therefore contains no information about which of the three buttons the user clicked.

What's the simplest way to detect when this button is clicked?

I want to respond when the user clicks Submit and ignore other button presses.

I see some other approaches on Stack Overflow using WKUserContentController, but they appear to require the web site to call something like:

window.webkit.messageHandlers.log.postMessage("submit");

I do not control this website so I cannot add this line in its source code, and I don't know the best way to inject it in the correct place using WKWebView.

like image 906
Aaron Brager Avatar asked Dec 08 '18 21:12

Aaron Brager


2 Answers

User Scripts are JS that you inject into your web page at either the start of the document load or after the document is done loading. User scripts are extremely powerful because they allow client-side customization of web page, allow injection of event listeners and can even be used to inject scripts that can in turn call back into the Native app. The following code snippet creates a user script that is injected at end of document load. The user script is added to the WKUserContentController instance that is a property on the WKWebViewConfiguration object.

// Create WKWebViewConfiguration instance
  var webCfg:WKWebViewConfiguration = WKWebViewConfiguration()

  // Setup WKUserContentController instance for injecting user script
  var userController:WKUserContentController = WKUserContentController()

  // Get script that's to be injected into the document
  let js:String = buttonClickEventTriggeredScriptToAddToDocument()

  // Specify when and where and what user script needs to be injected into the web document
  var userScript:WKUserScript =  WKUserScript(source: js, 
                                         injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
                                         forMainFrameOnly: false)

  // Add the user script to the WKUserContentController instance
  userController.addUserScript(userScript)

  // Configure the WKWebViewConfiguration instance with the WKUserContentController
  webCfg.userContentController = userController;

Your web page can post messages to your native app via the window.webkit.messageHandlers.<name>.postMessage (<message body>) method. Here, “name” is the name of the message being posted back. The JS can post back any JS object as message body and the JS object would be automatically mapped to corresponding Swift native object. The following JS code snippet posts back a message when a button click event occurs on a button with Id “ClickMeButton”.

var button = document.getElementById("clickMeButton");
button.addEventListener("click", function() {
            varmessageToPost = {'ButtonId':'clickMeButton'};
            window.webkit.messageHandlers.buttonClicked.postMessage(messageToPost);
        },false);

In order to receive messages posted by your web page, your native app needs to implement the WKScriptMessageHandler protocol. The protocol defines a single required method. The WKScriptMessage instance returned in the callback can be queried for details on the message being posted back.

func userContentController(userContentController: WKUserContentController,
                           didReceiveScriptMessage message: WKScriptMessage) {

        if let messageBody:NSDictionary= message.body as? NSDictionary{
            // Do stuff with messageBody
        }

    }

Finally, the native class that implements WKScriptMessageHandler protocol needs to register itself as a message handler with the WKWebView as follows:

// Add a script message handler for receiving  "buttonClicked" event notifications posted 
// from the JS document
userController.addScriptMessageHandler(self, name: "buttonClicked")
like image 121
Kousic Avatar answered Nov 01 '22 05:11

Kousic


You can always inject source code using evaluateJavaScript(_:) on the web view. From there, either replace the event handler on the buttons (posting a message and then invoking the original function in the new handler) or add an event handler on the buttons or an ancestor element that captures click events and posts a message. (The original event handler will also run.)

document.getElementById("submit-button").addEventListener("click", function () {
   window.webkit.messageHandlers.log.postMessage("submit");
});

If the buttons don’t have an ID, you can add a handler on the document that captures all (bubbling) click events and then posts a message based on the event target (using the button’s text or location as a determinant).

like image 5
Constantino Tsarouhas Avatar answered Nov 01 '22 04:11

Constantino Tsarouhas