iOS WKWebview JSBridge实现

很多APP的开发已经转为Hybrid模式开发,即一部分页面由Native编写,一部分页面由前端技术编写。随着页面数量增多,前端和Native页面之间的通信是必须解决的问题,前端页面需要通过js代码调用到Native的功能,获取Native页面的数据等,Native页面也需要执行js代码获取前端页面的信息。本文实现一个简易版本的jsbridge功能,实现Native和前端页面的互相调用。

代码准备

在ViewController里面先添加一个WKWebview,并且布局WKWebview使其占满整个页面。

self.webview = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.view addSubview:self.webview];
self.webview.navigationDelegate = self;
    
//加载网络文件
 NSURL *url = [NSURL URLWithString:@"http://xxxx/FrontEnd/index.html"];
 NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
 [self.webview loadRequest:request];

这里webview的布局和ViewController的view相同,loadRequest方法加载了一个网页。这个网页后面会替换为本地的网页,网页在VSCode中开发并使用live server插件使网页代码实时生效。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <div id="d" style="width: 100px;height: 100px;background-color: aqua;">

    </div>
</body>
</html>

在VSCode中新建一个index.html并写入以上代码,使用live server插件运行这个网页。获取到网页地址后,替换webview代码中的url,在iOS的模拟器中可以看到网页加载成功。

Native调用js

Native调用js是比较简单的,直接使用webview的-evaluateJavaScript:completionHandler:方法。

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    
    
    NSLog(@"didFinishNavigation");
    
    [webView evaluateJavaScript:@"window.location.href" completionHandler:^(id _Nullable res, NSError * _Nullable error) {
    
    
        NSLog(@"%@", res);
    }];
}

比如在网页加载完成的回调里面,执行window.location.href,就可以获取到当前页面的跳转地址。

js调用Native

js调用Native相对麻烦些,首先Native代码需要注册一个jsbridge通道,指定通道的名字,然后js调用这个jsbridge。

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addScriptMessageHandler:self name:@"bridgeTest"];
    
self.webview = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];

这里注册了名为bridgeTest的jsbridg通道,WKWebViewConfiguration对象传给webview,这样webview加载的网页可以通过window.webkit.messageHandlers.bridgeTest.postMessage()调用到Native。同时,Native代码需要实现WKScriptMessageHandler协议,本文的例子直接使用ViewController实现这个协议,这个协议只有一个方法。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    
    
}

当前端执行window.webkit.messageHandlers.bridgeTest.postMessage()时,Native的这段代码会执行到,前端传入的参数可以在message.body里面获取。

这样,js调用Native也实现了。总结来说,就是前端和Native需要约定一个bridge的名称,本文中的名称为bridgeTest。然后Native代码需要把WKWebViewConfiguration对象传给webview,最后,Native代码需要实现WKScriptMessageHandler协议。

可以看到这种调用方法,js获取不到Native的回调,为了获取到Native的回调,前端代码还需要保存callback,并且生成callbackId传给Native,Native执行完js的指令之后,把数据和callbackId一起传回给前端,前端最终完成callback的调用。所以这里前段还需要一段代码,用于保存callback,另外还要注册一个window里的方法,方便Native把数据和callbackId回传给前端。


let callbackId = 0;
let callbackMap = {
    
    };

class WebJSBridge{
    
    
    static call(name, params, callback) {
    
    
        //生成callbackId
        let cbId = this.genCallbackId();
        //添加到callbackMap
        this.add(cbId, callback);

        //组装方法和参数
        let config = {
    
    name: name, params: params, callbackId: cbId};
        let string = JSON.stringify(config);
        window.webkit.messageHandlers.bridgeTest.postMessage(string); 
    }
    
    //生成callbackId
    static genCallbackId() {
    
    
        return `Webview_callback_${
      
      callbackId++}`
    }
    //添加callback
    static add(callbackId, callback) {
    
    
        callbackMap[callbackId] = {
    
    
            callback: callback
        }
    }
    
};

//注入全局方法,用于Native向h5回调
window.bridgeCallback = function(callbackId, res) {
    
    
    let cb = callbackMap[callbackId];
    let callback = cb["callback"];
    if (callback) {
    
    
        callback(res);
    }
    delete callbackMap.callbackId
}

WebJSBridge是向前端业务暴露的jsbridge类,

call方法完成调用Native的工作,genCallbackId方法生成callbackId的工作,add用于保存callback和callbackId。bridgeCallback是前端向window注册的方法,Native可以通过这个方法回调前端。

WebJSBridge类完成后,需要在前端业务代码里面实际引入并且使用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <script type="text/javascript" src="./WebJSBridge.js"></script>
    <div id="d" style="width: 100px;height: 100px;background-color: aqua;">

    </div>

    <script type="text/javascript">
        var oDiv = document.getElementById("d");
        oDiv.addEventListener("click", function(){
      
      
            WebJSBridge.call("jsbridgeMethod", "param", function(res) {
      
      
                console.log("recieve callback")
                console.log(res)
            });
        });
    </script>

</body>
</html>

index.html代码现在改成了这样,使用script标签引入了WebJSBridge.js,使用WebJSBridge.call调用Native代码。

WebJSBridge.call("jsbridgeMethod", "param", function(res) {
    
    
                console.log("recieve callback")
                console.log(res)
            });

WebJSBridge.call第一个参数是调用的方法名,第二个参数是方法传参,第三个参数是回调callback。

Native代码需要解析前端传过来的参数,执行相应的指令。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    
    
    //string转为dictionary
    NSString *bodyString = message.body;
    NSData *data = [bodyString dataUsingEncoding:kCFStringEncodingUTF8];
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingFragmentsAllowed error:nil];
    NSString *callBackId = dic[@"callbackId"];
    
    //添加alert提示
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"jsBridge" message:bodyString preferredStyle:UIAlertControllerStyleAlert];
    __weak typeof(self) weakSelf = self;
    UIAlertAction *confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    
    
        [weakSelf dismissViewControllerAnimated:YES completion:^{
    
    
            
        }];
    }];
    [alert addAction:confirm];
    [self presentViewController:alert animated:YES completion:^{
    
    
        
    }];
    
    //向js发起回调
    NSString *jsString = [NSString stringWithFormat:@"window.bridgeCallback(\"%@\", \"%@\")", callBackId, @"success"];
    [self.webview evaluateJavaScript:jsString completionHandler:^(id _Nullable res, NSError * _Nullable error) {
    
    
            
    }];
}

这里在收到前端调用的时候,弹出了一个alertController,然后使用window.bridgeCallback向前端回调了数据。

总结

iOS中WKWebview实现js和Native互相通信总体不复杂,本文给出的是简单的实现。在实际应用中Native代码还是要经过一些封装和抽象,用来向前端暴露更多的方法,实现更复杂的bridge功能。在前端向window注入回调方法的时候,也可以考虑在Native端注入。

猜你喜欢

转载自blog.csdn.net/u011608357/article/details/128474290