Wkwebview - Complex Communication Between JavaScript & Native Code

WKWebview - Complex communication between Javascript & native code

Unfortunately I couldn't find a native solution.

But the following workaround solved my problem

Use javascript promises & you can call the resolve function from your iOS code.

UPDATE

This is how you can use promise

In JS

   this.id = 1;
this.handlers = {};

window.onMessageReceive = (handle, error, data) => {
if (error){
this.handlers[handle].resolve(data);
}else{
this.handlers[handle].reject(data);
}
delete this.handlers[handle];
};
}

sendMessage(data) {
return new Promise((resolve, reject) => {
const handle = 'm'+ this.id++;
this.handlers[handle] = { resolve, reject};
window.webkit.messageHandlers.<yourHandler>.postMessage({data: data, id: handle});
});
}

in iOS

Call the window.onMessageReceive function with appropriate handler id

Async communication of swift and Javascriptcore

Do I understand you right, if you do not want your javscript to terminate after execution?

If I understood you wrong, maybe following helps (I am not at home at Mac to test, but maybe it works if you modify your code as follows).

Option 1: Blocks

The swiftFunc1 could look like this:

func swiftFunc1() {
myCtxt = JSContext()
myCtxt.setObject(unsafeBitCast(swiftFunc2, AnyObject.self), forKeyedSubscript: "swiftFunc2")
exportedToJS = exportToJS() //confirms to JSExport and uses @objc
myCtxt.evaluateScript("swiftFunc2()")
}

Your swiftFunc2 would look like this:

let swiftFunc2: @convention(block) Void -> Void = { 
// do something here
}

Your JS code would look like this:

function jsFunc1() {
swiftFunc2();
}

Option 2: JSExport
Your have an exported class which is accessible for all javascript:

import Foundation
import JavaScriptCore
@objc class JavascriptHandler: NSObject, JavascriptHandlerExport {
let context: JSContext = JSContext()

init () {
context.setObject(self, forKeyedSubscript: "MyJSHandler") // set the object name for self accessible in javascript code
}
func swiftFunc1() {
context.evaluateScript("MyJSHandler.swiftFunc2();")
}
func swiftFunc2 () {
// do something here
}
}

Your protocol for the exported class.Here you have to declare all properties and methods you want to use with Javascript.

import Foundation
import JavaScriptCore
@objc protocol JavascriptHandlerExport: JSExport {
func swiftFunc2 ( ) -> Void
}

With this it should be possible for you to call a function from javascript and still let it continue.
You can now access the functions of the class JavascriptHandler from Javascript like this in this example:

MyJSHandler.swiftFunc2();

If you want to seperate the class where your WebView is from the one where your JS logic lies that should also not be a problem. You should also be able to combine the block syntax with the JSExport method.

Let me know if it's not working/behaving as wanted.

How do I communicate from JS to Swift

It's not that complicated actually.

Firstly you have to add the script message handler into the content controller.

let contentController = WKUserContentController()
contentController.add(self, name: "derp") //name is the key you want the app to listen to.

Next up you have to assign the content controller into the configuration.

let config = WKWebViewConfiguration()
config.userContentController = contentController

then you need to assign the configuration to your web view.

let webView = WKWebView(frame: CGRect.zero, configuration: config) //set your own frame

and for the JS side.

var message = {'fruit':'apple'};
window.webkit.messageHandlers.derp.postMessage(message);

Lastly you have to let your view controller (I'm assuming that the webView is in a view controller) conform to the WKScriptMessageHandler protocol. I've added the rest of the codes into a sample view controller as requested.

class MyViewController: UIViewController, WKScriptMessageHandler {

override func viewDidLoad() {

super.viewDidLoad()

let contentController = WKUserContentController()
contentController.add(self, name: "derp")

let config = WKWebViewConfiguration()
config.userContentController = contentController

let webView = WKWebView(frame: self.view.frame, configuration: config) //you can consider using auto layout though
self.view.addSubview(webView)

//load your url here
}

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

//handle script messages here
//message.name is "derp"
//message.body is ["fruit":"apple"]
}
}

How do I pass a swift object to javascript (WKWebView / swift)

Passing a native object to javascript is complex, especially for WKWebView which runs in multi-process mode. Any operation regarding the native object needs to cross process boundary. WKWebView has no language binding support between native and javascript. However, WKWebView supports message passing API. You have to wrap it for complex interactions between JS and native.

I created a project named XWebView which provides language binding styled API based on the raw message passing of WKWebView. It's written in Swift.

Regarding your example, the object has to be injected in javascript namespace firstly:

let webView = WKWebView(frame: frame, configuration: WKWebViewConfiguration())
webView.loadPlugin(AllInfo(), namespace: "someInfo")

You can access the object in javascript:

console.log(window.someInfo.title);
window.someInfo.title = "Some title";

To expose an Swift object to javascript, properties and methods must be dynamic dispatching. This means, properties must be dynamic, methods must has @objc attribute. (See https://developer.apple.com/swift/blog/?id=27 for dynamic dispatching). For simple, inherit from NSObject.

iOS JavaScript bridge

There are a few libraries, but I didn't used any of these in big projects, so you might want to try them out:

  • WebViewJavascriptBridge: https://github.com/marcuswestin/WebViewJavascriptBridge
  • GAJavaScript: https://github.com/newyankeecodeshop/GAJavaScript

However, I think it's something simple enough that you might give it a try yourself. I personally did exactly this when I needed to do that. You might also create a simple library that suits your needs.

1. Execute JS methods from Objective-C

This is really just one line of code.

NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:@"your javascript code string here"];

More details on the official UIWebView Documentation.

2. Execute Objective-C methods from JS

This is unfortunately slightly more complex, because there isn't the same windowScriptObject property (and class) that exists on Mac OSX allowing complete communication between the two.

However, you can easily call from javascript custom-made URLs, like:

window.location = yourscheme://callfunction/parameter1/parameter2?parameter3=value

And intercept it from Objective-C with this:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:@"yourscheme"]) {
// parse the rest of the URL object and execute functions
}
}

This is not as clean as it should be (or by using windowScriptObject) but it works.

3. Listen to native JS events from Objective-C (for example DOM ready event)

From the above explanation, you see that if you want to do that, you have to create some JavaScript code, attach it to the event you want to monitor and call the correct window.location call to be then intercepted.

Again, not clean as it should be, but it works.



Related Topics



Leave a reply



Submit