This is the 8th day of my participation in Gwen Challenge.More article challenges

The demand of the H5

  • 1. After jumping to the controller loaded to H5, the NavigationBar of the system needs to be hidden, while the NavigationBar is rendered by H5 loaded by WebView. Click the back button on H5 to perform the page pop operation.

  • 2. After loading the WebView page, click the button on H5 to activate the native camera application (whether it is a system camera or a camera with customized UI, it is controlled by the native itself). After taking photos with the camera, the picture will be transmitted to H5 in the form of Base64 string.

  • 3. After loading the WebView, you need to transfer the token obtained natively to the H5 terminal.

Primary analysis and scheme determination:

  • In the first example, clicking the H5 button and making the native interface close is a typical JS call native.

  • The second example is typical intermodulation. JS first calls the native camera, and then the native calls the JS method to transfer the data in the native to JS after obtaining the image resources.

  • The third example and the first example is the same, also JS call native, but need to pay attention to the life cycle of the WebView, note is the life cycle of the WebView, I in the debugging time spare a few detour, later share.

JS calls native methods

Now that we’ve analyzed who called whom, it’s all about calling the function correctly.

The WKWebViewConfiguration class has a property called userContentController

open class WKWebViewConfiguration : NSObject, NSSecureCoding, NSCopying { /** @abstract The user content controller to associate with the web view. */ open var userContentController:  WKUserContentController }Copy the code

The userContentController property is specifically used to listen for METHODS called by JS. The WKUserContentController class of userContentController has a method that listens for handles to JS methods

open class WKUserContentController : NSObject, NSSecureCoding { /** @abstract Adds a script message handler to the main world used by page content itself. @param scriptMessageHandler The script message handler to add. @param name The name of the message handler. @discussion Calling  this method is equivalent to calling addScriptMessageHandler:contentWorld:name: with [WKContentWorld pageWorld] as the contentWorld argument. */ open func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String) }Copy the code

WKScriptMessageHandler is the agent that needs to be set, usually the WebView controller. Name is the JS method handle.

It is important to note that the WKScriptMessageHandler agent is best encapsulated to avoid cyclic references, as shown below

import Foundation import WebKit class WeakScriptMessageDelegate: NSObject {//MARK:- Set private weak var scriptDelegate: WKScriptMessageHandler! //MARK:- init(scriptDelegate: WKScriptMessageHandler) { self.scriptDelegate = scriptDelegate } } extension WeakScriptMessageDelegate: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { self.scriptDelegate.userContentController(userContentController, didReceive: message) } }Copy the code

Next, we can assign this configuration item to the WebView

private lazy var webView: WKWebView = {let config = WKWebViewConfiguration() config.userContentController.add(WeakScriptMessageDelegate(scriptDelegate: self), name: "SeasonCallback") let preferences = WKPreferences() preferences.javaScriptCanOpenWindowsAutomatically = true Config. preferences = preferences /// Let webView = WKWebView(frame: view.frame, configuration: Config) webView. AllowsBackForwardNavigationGestures = true / / / webView listening in webView. NavigationDelegate = self return webView }()Copy the code

We then implement the WKScriptMessageHandler agent at the controller level:

extension WebViewController: WKScriptMessageHandler {/// the native interface listens to JS run, intercepting the method in JS corresponding to the method registered in userContentController /// /// -parameters: /// - userContentController: WKUserContentController /// - message: WKScriptMessage contains the parameters already passed by the method name,WKScriptMessage, where the body can accept Allowed types are NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {print(" method name :\(message.name)") print(" parameter :\(message.body)") guard let MSG = message.body as? String], let value = msg["method"] else { return } if value == "back" { navigationController?.popViewController(animated: true) } } }Copy the code

In HTML, we need to register a JS method like this:

The method names registered with messageHandlers in JS must be exactly the same as the native method names! Here’s SeasonCallback.

Corresponding to the comments of the native method above, the body in WKScriptMessage is actually the argument passed by JS. Personally, I prefer to pass JSON in JS, so that it is convenient to transfer Dictionary in the native method and can pass multiple parameters.

The following method binds the button component click event in H5:

function jsButtonAction() {
    let argument = {
                        'method' : 'back',
                        'params' : {}
                    };

    window.webkit.messageHandlers.SeasonCallback.postMessage(argument);
}
Copy the code

This opens the way for JS to call native functions. We can register multiple handles, or pass different values in method to distinguish between different methods.

Note that in order to avoid circular references, we need to remove the JS handle registered for listening from the destructor of the controller:

deinit {
    webView.configuration.userContentController.removeScriptMessageHandler(forName: "SeasonCallback")
}
Copy the code

Call JS methods natively

Call way

Native JS is a little simpler, just call WebView method can be:

extension WKWebView {

    public func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo? = nil, in contentWorld: WKContentWorld, completionHandler: ((Result<Any, Error>) -> Void)? = nil)

}
Copy the code

For example, we wrote a function like this in H5 JS:

function callJS(text) { console.log(text); Return "Hello Swift!" ; }Copy the code

This is called in Swift

WebView. EvaluateJavaScript (" callJS (' this is from the parameters of the Swift transfer to JS ') ") {any error in print (any) print (error)}Copy the code

Call time

As for the third requirement before, after loading the WebView, it is necessary to transfer the token obtained natively to the H5 end.

Since the front end of our project is written in Vue, we added this code in main.js. We registered a method with the hook:

window.getToken = function(token) {
  Vue.prototype.$token = token;
}
Copy the code

I added this code to the WebView control:

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white

    view.addSubview(webView)

    if let url = URL(string: url) {
        let request = URLRequest(url: url)
        webView.load(request)

        webView.evaluateJavaScript("getToken('\(token)')") { any, error in
            print(any)
            print(error)
        }
    }
}
Copy the code

I immediately executed the JS function after webView.load(request), and the front-end friend couldn’t get the token at all!!

Why is that? ! Because I understand is in native viewDidLoad once you add the webView and load the request, you can run the webView. EvaluateJavaScript. $token = function(token) {vue.prototype.$token = token; } This code is registered after the page is initialized.

So I’m going to call this JS method and wait until the WebView is loaded!!

Add WKNavigationDelegate to the WebView

webView.navigationDelegate = self
Copy the code

In the WKNavigationDelegate agent, func webView(_ webView: WKWebView, didFinish Navigation: WKNavigation!) EvaluateJavaScript in this callback correctly passes the token to the H5 page.

Extension WebViewController: WKNavigationDelegate {/// call /// /// -parameters: /// -webView: Implementation of the agent webView // -navigation: current navigation func webView(_ webView: WKWebView, didFinish Navigation: WKNavigation!) { webView.evaluateJavaScript("CUSCGetToken('\(token)')") { any, error in print(any) print(error) } } }Copy the code

conclusion

  • There is nothing particularly difficult about intercalling Swift and JS methods. The first thing you need to know is who is calling whom. Once you know whether Swift is calling JS or JS is calling Swift, all that remains is to use the correct method.

  • In Swift and JS, the method name and parameter transmission format are well established, and the function and parameters are transferred in strict accordance with the rules formulated by developers on both sides, so as to successfully call the function and communicate.

  • Knowing some Html initialization cycles will help you when Swift calls JS.

Tomorrow to continue

There will probably be an interludes of Flutter and JS interaction solutions and examples tomorrow.