iOS 14 API WKScriptMessageHandlerWithReply 如何与来自 iOS 的 JavaScript 通信?

How does the iOS 14 API WKScriptMessageHandlerWithReply work for communicating with JavaScript from iOS?

iOS 14 引入了一种接收 javascript 调用并使用 WKScriptMessageHandlerWithReply 而不是 WKScriptMessageHandler(在 WebKit 视图中)提供响应的新方法。但是文档基本上是不存在的。这是如何工作的?

我深入研究了一下,发现它使用 Javascript Promise 来提供回调机制(并且从应用程序代码返回到 javascript 的响应必须是异步的)。

下面是一些示例代码来说明:

swift代码:

import UIKit
import WebKit
import PureLayout

final class ViewController: UIViewController {

    var webView : WKWebView?
    let JavaScriptAPIObjectName = "namespaceWithinTheInjectedJSCode"
    
    override func viewDidLoad() {
        super.viewDidLoad()
                
        //-------
        
        guard let scriptPath = Bundle.main.path(forResource: "script", ofType: "js"),
              let scriptSource = try? String(contentsOfFile: scriptPath) else { return }

        let userScript = WKUserScript(source: scriptSource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)

        let config = WKWebViewConfiguration()

        let userContentController = WKUserContentController()
        userContentController.addUserScript(userScript)
        
        // REQUIRES IOS14
        if #available(iOS 14, *){
            userContentController.addScriptMessageHandler(self, contentWorld: .page, name: JavaScriptAPIObjectName)
        }

        config.userContentController = userContentController
        
        webView = WKWebView(frame: .zero, configuration: config)
                
        if let webView = webView{
            view.addSubview(webView)
            webView.autoPinEdgesToSuperviewMargins() // using PureLayout for easy AutoLayout syntax

            if let htmlPath = Bundle.main.url(forResource: "page", withExtension: "html"){
                webView.loadFileURL( htmlPath, allowingReadAccessTo: htmlPath);
            }
        }
    }

    // need to deinit and remove webview stuff
    deinit {
        if let webView = webView{
            let ucc = webView.configuration.userContentController
            ucc.removeAllUserScripts()
            ucc.removeScriptMessageHandler(forName:JavaScriptAPIObjectName)
        }
    }
}

extension ViewController: WKScriptMessageHandlerWithReply {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
        if message.name == JavaScriptAPIObjectName, let messageBody = message.body as? String {
            print(messageBody)
            replyHandler( 2.2, nil ) // first var is success return val, second is err string if error
        }
    }
}

这是通过 Swift 代码加载并注入网页的 script.js:

function sampleMethodTheHTMLCanCall( inputInfo, successFunc, errorFunc ) {
    
    var promise = window.webkit.messageHandlers.namespaceWithinTheInjectedJSCode.postMessage( inputInfo );
    
    promise.then(
      function(result) {
        console.log(result); // "Stuff worked!"
        successFunc( result )
      },
      function(err) {
        console.log(err); // Error: "It broke"
        errorFunc( err )
      });
}

这是可以调用应用程序代码的 page.html 示例 HTML:

<html>
    <meta name="viewport" content="width=device-width" />
        <script>
            function handleInfoFromApp( fromApp ){
                document.getElementById("valToWrite").innerHTML = fromApp;
            }
            function handleError( err ){
            
            }
        </script>

    <h1 id="valToWrite">Hello</h1>
    <button onclick="sampleMethodTheHTMLCanCall( 'inputInfo', handleInfoFromApp, handleError )">Load Info from App</button>
    
</html>

上面的 HTML 提供的函数稍后会在 javascript 发起的请求成功或失败时由应用程序扩展代码调用。