iOS 14 API WKScriptMessageHandlerWithReply 如何从 iOS 与 JavaScript 进行通信?

Jas*_*son 7 javascript webkit ios

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

Jas*_*son 18

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

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

快速代码:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是通过 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 )
      });
}
Run Code Online (Sandbox Code Playgroud)

这是可以调用应用程序代码的 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>
Run Code Online (Sandbox Code Playgroud)

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