WKWebViewでは、Webkitメッセージハンドラーを使用してObjectiveC/Swiftコードを呼び出すことができます。例:webkit.messageHandlers.<handler>.pushMessage(message)
パラメータのない単純なjavascript関数に適しています。だが;
残念ながら、ネイティブなソリューションが見つかりませんでした。
しかし、次の回避策は私の問題を解決しました
JavaScriptの約束を使用し、iOSコードから解決関数を呼び出すことができます。
[〜#〜] 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で
window.onMessageReceive
適切なハンドラーIDを持つ関数
WkWebViewを使用してネイティブコードからJSに戻り値を取得する方法があります。それは少しハックですが、問題なく私のためにうまく動作し、本番アプリは多くの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.");
}
}
私の場合、引数を渡すためにdata type = xyz、name = xyz、data = xyzを渡します。私のToUiSynch()コードはリクエストを処理し、単純な戻り値としてJSに戻る文字列を常に返します。
JSでは、書式設定された引数文字列を使用してPrompt()関数を呼び出し、戻り値を取得するだけです。
return Prompt ("type=" + type + ";name=" + name + ";data=" + (typeof data === "object" ? JSON.stringify ( data ) : data ));
この答えは、ネイサンブラウンの answer 上記のアイデアを使用しています。
私の知る限り、現在、データをjavascriptsynchronousに戻す方法はありません。うまくいけばAppleは将来のリリースで解決策を提供するでしょう。
ハックは、jsからのプロンプト呼び出しをインターセプトすることです。 Appleは、jsがアラートやプロンプトなどを呼び出すときにネイティブポップアップデザインを表示するために、この機能を提供しました。 param)およびこのプロンプトに対するユーザーからの応答はjsに返されます(これを戻りデータとして利用します)
文字列のみを返すことができます。これは同期的に発生します。
上記のアイデアを次のように実装できます。
javascript end:で、Swiftメソッドを次のように呼び出します。
_ function callNativeApp(){
console.log("callNativeApp called");
try {
//webkit.messageHandlers.callAppMethodOne.postMessage("Hello from JavaScript");
var type = "SJbridge";
var name = "functionOne";
var data = {name:"abc", role : "dev"}
var payload = {type: type, functionName: name, data: data};
var res = Prompt(JSON.stringify (payload));
//{"type":"SJbridge","functionName":"functionOne","data":{"name":"abc","role":"dev"}}
//res is the response from Swift method.
} catch(err) {
console.log('The native context does not exist yet');
}
}
_
Swift/xcode endで次のようにします:
プロトコルWKUIDelegate
を実装し、次のようにWKWebviews uiDelegate
プロパティに実装を割り当てます。
_self.webView.uiDelegate = self
_
次に、この_func webView
_を記述して、javascriptからのPrompt
の要求をオーバーライド(?)/インターセプトします。
_func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt Prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
if let dataFromString = Prompt.data(using: .utf8, allowLossyConversion: false) {
let payload = JSON(data: dataFromString)
let type = payload["type"].string!
if (type == "SJbridge") {
let result = callSwiftMethod(Prompt: payload)
completionHandler(result)
} else {
AppConstants.log("jsi_", "unhandled Prompt")
completionHandler(defaultText)
}
}else {
AppConstants.log("jsi_", "unhandled Prompt")
completionHandler(defaultText)
}}
_
completionHandler()
を呼び出さないと、jsの実行は続行されません。次に、jsonを解析し、適切なSwiftメソッドを呼び出します。
_ func callSwiftMethod(Prompt : JSON) -> String{
let functionName = Prompt["functionName"].string!
let param = Prompt["data"]
var returnValue = "returnvalue"
AppConstants.log("jsi_", "functionName: \(functionName) param: \(param)")
switch functionName {
case "functionOne":
returnValue = handleFunctionOne(param: param)
case "functionTwo":
returnValue = handleFunctionTwo(param: param)
default:
returnValue = "returnvalue";
}
return returnValue
}
_
質問1の回避策があります。
JavaScriptを使用したPostMessage
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);
}
}];
}
ネイティブアプリとWebView(JS)間の双方向通信を実現するために、JSでpostMessage
とネイティブコードでevaluateJavaScript
を使用して、この問題を解決することができました。
高レベルのソリューションは次のとおりです。
getDataFromNative
と呼び、別のコールバック関数を呼び出します(callbackForNative
と呼びます)。callbackForNative
を必要な関数に再割り当てしますpostMessage
を使用してWebViewからNativeを呼び出しますuserContentController
を使用して、WebView(JS)からの着信メッセージをリッスンしますevaluateJavaScript
を使用して、必要なパラメーターでgetDataFromNative
JS関数を呼び出しますコードは次のとおりです。
JS:
// Function to get data from Native
window.getDataFromNative = function(data) {
window.callbackForNative(data)
}
// Empty callback function, which can be reassigned later
window.callbackForNative = function(data) {}
// Somewhere in your code where you want to send data to the native app and have it call a JS callback with some data:
window.callbackForNative = function(data) {
// Do your stuff here with the data returned from the native app
}
webkit.messageHandlers.YOUR_NATIVE_METHOD_NAME.postMessage({ someProp: 'some value' })
ネイティブ(Swift):
// Call this function from `viewDidLoad()`
private func setupWebView() {
let contentController = WKUserContentController()
contentController.add(self, name: "YOUR_NATIVE_METHOD_NAME")
// You can add more methods here, e.g.
// contentController.add(self, name: "onComplete")
let config = WKWebViewConfiguration()
config.userContentController = contentController
self.webView = WKWebView(frame: self.view.bounds, configuration: config)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("Received message from JS")
if message.name == "YOUR_NATIVE_METHOD_NAME" {
print("Message from webView: \(message.body)")
sendToJavaScript(params: [
"foo": "bar"
])
}
// You can add more handlers here, e.g.
// if message.name == "onComplete" {
// print("Message from webView from onComplete: \(message.body)")
// }
}
func sendToJavaScript(params: JSONDictionary) {
print("Sending data back to JS")
let paramsAsString = asString(jsonDictionary: params)
self.webView.evaluateJavaScript("getDataFromNative(\(paramsAsString))", completionHandler: nil)
}
func asString(jsonDictionary: JSONDictionary) -> String {
do {
let data = try JSONSerialization.data(withJSONObject: jsonDictionary, options: .prettyPrinted)
return String(data: data, encoding: String.Encoding.utf8) ?? ""
} catch {
return ""
}
}
追伸私はフロントエンド開発者なので、JSには非常に熟練していますが、Swiftにはほとんど経験がありません。
P.S.2 WebViewがキャッシュされていないことを確認してください。キャッシュしないと、HTML/CSS/JSが変更されてもWebViewが変更されない場合にイライラする可能性があります。
参照:
このガイドは私を大いに助けてくれました: https://medium.com/@JillevdWeerd/creating-links-between-wkwebview-and-native-code-8e998889b5