Handling JavaScript Events in Wkwebview

Handling JavaScript events in WKWebView

you can use a WKUserScript and add it to the userContentController of the WKWebView's configuration.

    let config = WKWebViewConfiguration()
let source = "document.addEventListener('click', function(){ window.webkit.messageHandlers.iosListener.postMessage('click clack!'); })"
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
config.userContentController.addUserScript(script)
config.userContentController.add(self, name: "iosListener")
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)

this will make the script and inject it into the page when the document is finished loading. Now, you need to implement the WKScriptMessageHandler protocol to receive the message:

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("message: \(message.body)")
// and whatever other actions you want to take
}

Catch Javascript Event in iOS WKWebview with Swift

Based on the answer from @Alex Pelletier, which really helped me, here is the solution the my question.

In my "loadView()" function, here is what I have :

let contentController = WKUserContentController();
contentController.addScriptMessageHandler(
self,
name: "callbackHandler"
)

let config = WKWebViewConfiguration()
config.userContentController = contentController

webView = WKWebView(frame: CGRectZero, configuration: config)
webView.navigationDelegate = self
view = webView

My function to handle the Javascript event which is sent to Swift :

func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
{
if(message.name == "callbackHandler") {
print("Launch my Native Camera")
}
}

... And finally, my Javascript (jQuery) code when a click happens on my camera button (in HTML) :

$(document).ready(function() {

function callNativeApp () {
try {
webkit.messageHandlers.callbackHandler.postMessage("camera");
} catch(err) {
console.log('The native context does not exist yet');
}
}

$(".menu-camera-icon").click(function() {
callNativeApp();
});
});

I hope it will help someone else :-) !

WKScriptMessageHandler won't Listen to 'onclick' or 'click' event on a button element on a webpage. The web page is developed using Reactjs

After 4 years I got a chance to work on the similar codebase and found out the exact reason of the failure. The exact reason are as below:

  1. Unnecessary and erroneous assignment of userContentController. This didn't replace the actual WKUserContentController created at the time of instantiation of WKWebView. We added the script handler erroneously like so controller.add(self, name: eventname). Instead we should have added the script handler directly to the webviews usercontentcontroller instance like so
//WRONG: To set userContentController and add script handler 
wkWebView.configuration.userContentController = controller
controller.add(self, name: eventname)

//CORRECT: To add script handler
wkWebView.configuration.userContentController.add(self, name: eventname)

Thanks to @Ashok for pointing this out.


  1. The timing of evaluating the javascript to add Event Listener to each element was not perfect. By the time our javascript got evaluated the elements had not been created and thus no event listeners were added. There are two ways to perfect the time of evaluation of the javascript. We can introduce a delay which obviously is not a clean solution. The cleaner solution is to use WKUserScript with the injectionTime set to .atDocumentEnd and add the script to the userContentController
let eventName = "SOME_EVENT"
let js = "var elements = document.getElementsByClassName('btn button_btn button_primary button_md button_block'); for (var i = 0 ; i < elements.length; i++) { elements[i].addEventListener('click', function(){ window.webkit.messageHandlers.\(eventname).postMessage("MESSAGE") }); }"
let userScript = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
wkWebView.configuration.userContentController.addUserScript(userScript)

  1. I would like to thanks @Abbey Jackson for providing hints to the solution for my case. Her answer pointed me to the fact that onClick, onclick should not be used for adding event listeners to the elements. Instead use click for the same.
// Relevant while coding for WKWebView on iOS

// WRONG: Using onClick or onclick does not register the event listener
elements[i].addEventListener("click", function(){ YOUR_HANDLER_CODE } }

// CORRECT: Use click instead of onClick or onclick for adding event listener
elements[i].addEventListener("click", function(){ YOUR_HANDLER_CODE } }

NOTE: I could pefectly pass a literal dictionary like {"foo": "bar"} in place of "YOUR_MESSAGE". It didnt cause me a problem. Although it could cause a problem if it contained items which couldn't be easily ported to Swift.

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

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

Adding a click event listener on WKWebView not working

For some reason, Everything started working after adding the code after evaluating that javascript. I am still unable to know why.

Here is the code for the full extension so far:

extension WKWebView {
/// Type of HTML element to get from DOM
enum ElementType {
/// ID Element
case id
/// Class element
case `class`
}

/// List of errors for WKWebView injection
enum InjectionError: Error {
/// The Listener is already added
case listenerAlreadyAdded
}

/// Cahnges the CSS Visibiltiy
/// - Parameters:
/// - elementID: The name of the element
/// - isVisible: Wether or not is visible
/// - elementType: The type of element to get
/// - completion: Callback triggered went script has been appended to WKWebView
func changeCSSVisibility(elementID: String, isVisible: Bool, elementType: ElementType, completion: ((Error?)->Void)?) {
let script: String

switch elementType {
case .id:
script = "document.getElementById('\(elementID)').style.visibility = \(isVisible ? "'visible'":"'hidden'");"
case .class:
script =
"""
[].forEach.call(document.querySelectorAll('.\(elementID)'), function (el) {
el.style.visibility = \(isVisible ? "'visible'":"'hidden'");
});
"""
}

evaluateJavaScript(script, completionHandler: { (result, error) in
completion?(error)
})
}

/// Adds a event listener that will be call on WKScriptMessageHandler - didReceiveMessage
/// - Parameters:
/// - elementID: The name of the element
/// - callbackID: The ID for the callback
/// - elementType: The type of element to get
/// - completion: Callback triggered went script has been appended to WKWebView
func addEventListener(elementID: String, callbackID: String, elementType: ElementType, handler: WKScriptMessageHandler, completion: ((Error?)->Void)?) {
let element: String

switch elementType {
case .id:
element = "document.getElementById('\(elementID)')"
case .class:
element = "document.getElementsByClassName('\(elementID)')[0]"
}

let scriptString = """
function callback () {
console.log('\(callbackID) clicked!')
window.webkit.messageHandlers.\(callbackID).postMessage({
message: 'WKWebView-onClickListener-\(callbackID)'
});
}

\(element).addEventListener('click', callback);
"""

if configuration.userContentController.userScripts.first(where: { $0.source == scriptString }) == nil {
evaluateJavaScript(scriptString) { [weak self] (result, error) -> Void in
guard let self = self else { return }

if let error = error {
completion?(error)
} else {
self.configuration.userContentController.removeScriptMessageHandler(forName: callbackID)
self.configuration.userContentController.add(handler, name: callbackID)
self.configuration.userContentController.addUserScript(WKUserScript(source: scriptString, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
}
}
} else {
completion?(InjectionError.listenerAlreadyAdded)
}
}
}

Issue with observing WKWebView URL changes via JavaScript events

Before you go any further, I would strongly recommend that you use the modern Swift method of observing:

var webViewURLObserver: NSKeyValueObservation?

override func viewDidLoad() {
// ...
webViewURLObserver = webview.observe(\.url, options: .new) { webview, change in
print("URL: \(String(describing: change.newValue))")
// The webview parameter is the webview whose url changed
// The change parameter is a NSKeyvalueObservedChange
// n.b.: you don't have to deregister the observation;
// this is handled automatically when webViewURLObserver is dealloced.
}
}

Doing this makes your code much cleaner and easier to reason about while you're debugging the issue.



Related Topics



Leave a reply



Submit