web-dev-qa-db-ja.com

WKWebview-Javascriptとネイティブコード間の複雑な通信

WKWebViewでは、Webkitメッセージハンドラーを使用してObjectiveC/Swiftコードを呼び出すことができます。例:webkit.messageHandlers.<handler>.pushMessage(message)

パラメータのない単純なjavascript関数に適しています。だが;

  1. JSコールバック関数をパラメーターとして使用してネイティブコードを呼び出すことは可能ですか?
  2. ネイティブコードからJS関数に値を返すことは可能ですか?
24
Clement Prem

残念ながら、ネイティブなソリューションが見つかりませんでした。

しかし、次の回避策は私の問題を解決しました

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を持つ関数

10
Clement Prem

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 ));
9
Nathan Brown

XWebView が現在最良の選択です。ネイティブオブジェクトをjavascript環境に自動的に公開できます。

質問2では、JSからネイティブへの同期通信は不可能であるため、JSコールバック関数をネイティブに渡して結果を取得する必要があります。

詳細については、 sample appを確認してください。

3
soflare

この答えは、ネイサンブラウンの 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で次のようにします:

  1. プロトコルWKUIDelegateを実装し、次のようにWKWebviews uiDelegateプロパティに実装を割り当てます。

    _self.webView.uiDelegate = self
    _
  2. 次に、この_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
}
_
3
Sasuke Uchiha

質問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);
        }
    }];
}
1
张建伟

ネイティブアプリとWebView(JS)間の双方向通信を実現するために、JSでpostMessageとネイティブコードでevaluateJavaScriptを使用して、この問題を解決することができました。

高レベルのソリューションは次のとおりです。

  • WebView(JS)コード:
    • ネイティブからデータを取得する一般的な関数を作成します(ネイティブの場合はgetDataFromNativeと呼び、別のコールバック関数を呼び出します(callbackForNativeと呼びます)。
    • いくつかのデータを使用してNativeを呼び出し、応答が必要な場合は、次を実行します。
      • 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

0
Liran H