WKWebview – Javascript和本地代码之间的复杂通信

在WKWebView中,我们可以使用webkit消息处理程序调用ObjectiveC / swift代码,例如: webkit.messageHandlers.<handler>.pushMessage(message)

它适用于没有参数的简单的JavaScript函数。 但;

  1. 有可能调用本地代码与JScallback函数作为参数?
  2. 是否有可能从本地代码返回一个值到JS函数?

不幸的是我找不到本机解决scheme。

但下面的解决方法解决了我的问题

使用JavaScript承诺&你可以从你的iOS代码调用parsing函数。

UPDATE

这是你如何使用承诺

在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}); }); } 

在iOS中

使用适当的处理程序ID调用window.onMessageReceive函数

有一种方法可以使用WkWebView从本机代码中获取返回值。 这是一个小黑客,但工作正常,没有问题,我们的生产应用程序使用大量的JS /本地通信。

在分配给WKWebView的WKUiDelegate中,覆盖RunJavaScriptTextInputPanel。 这使用委托处理JS提示符函数的方式来完成此操作:

  public override void RunJavaScriptTextInputPanel (WebKit.WKWebView webView, string prompt, string defaultText, WebKit.WKFrameInfo frame, Action<string> completionHandler) { // this is used to pass synchronous messages to the ui (instead of the script handler). This is because the script // handler cannot return a value... if (prompt.StartsWith ("type=", StringComparison.CurrentCultureIgnoreCase)) { string result = ToUiSynch (prompt); completionHandler.Invoke ((result == null) ? "" : result); } else { // actually run an input panel base.RunJavaScriptTextInputPanel (webView, prompt, defaultText, frame, completionHandler); //MobApp.DisplayAlert ("EXCEPTION", "Input panel not implemented."); } } 

在我的情况下,我传递数据types= xyz,名称= xyz,数据= xyz传递参数。我的ToUiSynch()代码处理请求,并总是返回一个string,它返回到JS作为一个简单的返回值。

在JS中,我简单地使用格式化的argsstring调用prompt()函数并获取返回值:

 return prompt ("type=" + type + ";name=" + name + ";data=" + (typeof data === "object" ? JSON.stringify ( data ) : data )); 

XWebView是目前最好的select。 它可以自动将本地对象暴露给JavaScript环境。

对于问题2,您必须将JScallback函数传递给本地以获取结果,因为从JS到本机的同步通信是不可能的。

有关更多详情,请查看示例应用程序。

我有一个问题1的解决方法。

PostMessage与JavaScript

 window.webkit.messageHandlers.<handler>.postMessage(function(data){alert(data);}+""); 

在Objective-C项目中处理它

 -(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ NSString *callBackString = message.body; callBackString = [@"(" stringByAppendingString:callBackString]; callBackString = [callBackString stringByAppendingFormat:@")('%@');", @"Some RetString"]; [message.webView evaluateJavaScript:callBackString completionHandler:^(id _Nullable obj, NSError * _Nullable error) { if (error) { NSLog(@"name = %@ error = %@",@"", error.localizedDescription); } }]; } 

你不能。 作为@Clement提到,你可以使用promise并调用resolve函数。 相当不错(虽然使用Deferred – 现在被认为是反模式)的例子是GoldenGate 。

在Javascript中,您可以使用两种方法创build对象:调度和解决:(我已经编译了CS到JS以便于阅读)

 this.Goldengate = (function() { function Goldengate() {} Goldengate._messageCount = 0; Goldengate._callbackDeferreds = {}; Goldengate.dispatch = function(plugin, method, args) { var callbackID, d, message; callbackID = this._messageCount; message = { plugin: plugin, method: method, "arguments": args, callbackID: callbackID }; window.webkit.messageHandlers.goldengate.postMessage(message); this._messageCount++; d = new Deferred; this._callbackDeferreds[callbackID] = d; return d.promise; }; Goldengate.callBack = function(callbackID, isSuccess, valueOrReason) { var d; d = this._callbackDeferreds[callbackID]; if (isSuccess) { d.resolve(valueOrReason[0]); } else { d.reject(valueOrReason[0]); } return delete this._callbackDeferreds[callbackID]; }; return Goldengate; })(); 

然后你打电话

  Goldengate.dispatch("ReadLater", "makeSomethingHappen", []); 

而从iOS方面来说:

  func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { let message = message.body as! NSDictionary let plugin = message["plugin"] as! String let method = message["method"] as! String let args = transformArguments(message["arguments"] as! [AnyObject]) let callbackID = message["callbackID"] as! Int println("Received message #\(callbackID) to dispatch \(plugin).\(method)(\(args))") run(plugin, method, args, callbackID: callbackID) } func transformArguments(args: [AnyObject]) -> [AnyObject!] { return args.map { arg in if arg is NSNull { return nil } else { return arg } } } func run(plugin: String, _ method: String, _ args: [AnyObject!], callbackID: Int) { if let result = bridge.run(plugin, method, args) { println(result) switch result { case .None: break case .Value(let value): callBack(callbackID, success: true, reasonOrValue: value) case .Promise(let promise): promise.onResolved = { value in self.callBack(callbackID, success: true, reasonOrValue: value) println("Promise has resolved with value: \(value)") } promise.onRejected = { reason in self.callBack(callbackID, success: false, reasonOrValue: reason) println("Promise was rejected with reason: \(reason)") } } } else { println("Error: No such plugin or method") } } private func callBack(callbackID: Int, success: Bool, reasonOrValue: AnyObject!) { // we're wrapping reason/value in array, because NSJSONSerialization won't serialize scalar values. to be fixed. bridge.vc.webView.evaluateJavaScript("Goldengate.callBack(\(callbackID), \(success), \(Goldengate.toJSON([reasonOrValue])))", completionHandler: nil) } 

请考虑这个有关承诺的伟大的文章