WKWebView-WKScriptMessageHandler实际应用(一)

3,305 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金

通过问题看本质!!!

前文回顾

项目背景

我们在项目中都在使用WKWebView了,而WebKit引入了javascript引擎,只需要在WKWebView初始化的时候,添加脚本信息处理器WKScriptMessageHandler,让一个对象具有脚本信息处理能力,只要我们遵循协议方法,就能进行通信了。

项目中(HTML)统一以下方法进行交互:

AndroidTCXJSbridge.postMessage(string json);
iOS-WKWebView: window.webkit.messageHandlers.TCXJSbridge.postMessage(string json);

iOS客户端中,我们只要在以下的回调方法处理业务逻辑。


- (void)userContentController:(WKUserContentController *)userContentController 
		didReceiveScriptMessage:(WKScriptMessage *)message{
    if ([message.name isEqualToString:@"TCXJSbridge"]) {
        //解析数据,执行相应的代码
     }
}

定义协议

问:实现JS和客户端通信前,需要做什么准备呢?

答:JS和原生交互时,要提前定好通信协议。就像我们调试接口一样,后端提供的数据格式是固定的,这样更加利于代码的封装和后期的维护。

JS通信格式

{
    "data": {
      //具体的数据
      appid:"20201123"
    },
    "callback": "callbackFunc",//回调js 函数,由js 定义
    "name": "device.getAppVersion" //原生api名字
}

iOS客户端响应格式

{
   status:"404",
   errorMsg:"没有该JS API",
   data: {
      //data 
      appid:"20201123"
   }
}

代码实现

准备工作做完后,接下来就是实际操作了。

JS调用原生代码,与iOS客户端交互实现,比如获取经纬度功能

//获取经纬度
function getLocation(){
    var json ={
       data:{
            appid:"20201123",
        },
        name:"map.getLocation",
        callback:"callbackFunc"
    };
    window.webkit.messageHandlers.TCXJSbridge.postMessage(JSON.stringify(json));
}

// 开发工作中,测试回调代码
function callbackFunc(result){
    alert(result);
    console.log(JSON.parse(result));
}

iOS客户端接收到数据,处理业务逻辑,并回传数据到HTML,实现双方通信。

//初始化时,添加handler,要注意循环引用问题
TCXWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[TCXWebViewScriptMessageDelegate alloc] initWithDelegate:self];
[self.webView.configuration.userContentController addScriptMessageHandler:weakScriptMessageDelegate name:@"TCXJSbridge"];

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    if ([message.name isEqualToString:@"TCXJSbridge"]) {
        //解析数据,执行相应的代码

        NSDictionary *body = message.body;
        if (!body) {
            return;
        }
        if ([body isKindOfClass:[NSDictionary class]]) {
            NSString *actionName = body[@"name"];
            if (@"map.getLocation" isEqualToString:actionName]){
                //获取经纬度
                NSString *callback = body[@"callback"];
                if(callback){
                    NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:2];
                    [dictionary setValue:@"39.916527" forKey:@"latitude"];
                    [dictionary setValue:@"116.397128" forKey:@"longitude"];
                    NSString *jsCallback = [self getJsCallWithCallbackFunc:actionName callDic:dictionary];
                    //获取到js的方法,回传数据
                    [self.webView evaluateJavaScript:jsCallback completionHandler:^(id _Nullable object, NSError * _Nullable error) {
                        NSLog(@"object = %@,error = %@",object,error);
                    }];
                }
            }else if (@"user.getInfomation" isEqualToString:actionName]){
                //获取用户信息
            }else{
               //其他操作
            }
        }
    }
}

//组装JS回调的数据,js方法的参数为json字符串
-(NSString*)getJsCallWithCallbackFunc:(NSString*)callbackFunc callDic:(NSDictionary*)callDic{
    NSMutableDictionary *dic = [NSMutableDictionary new];
    [dic setValue:@(200) forKey:@"status"];
    [dic setValue:callDic ? callDic : @{} forKey:@"data"];
    [dic setValue:@"" forKey:@"errorMsg"];
    NSData * jsonData = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
    NSString * jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    NSString * jsCall = [NSString stringWithFormat:@"%@('%@')", callbackFunc, jsonString];
    return jsCall;
}

自定义一个类,实现<WKScriptMessageHandler>协议。

#import <Foundation/Foundation.h>
@interface TCXWebViewScriptMessageDelegate : NSObject<WKScriptMessageHandler>

@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;

- (instancetype**)initWithDelegate(id<WKScriptMessageHandler>)scriptDelegate;
@end


@implementation TCXWebViewScriptMessageDelegate

-(void)dealloc{
    NSLog(@"THKWebViewScriptMessageDelegate dealloc");

}

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {

    self = [super init];

    if (self) {
        _scriptDelegate = scriptDelegate;
    }
    return self;
}

#pragma mark - WKScriptMessageHandler

//遵循WKScriptMessageHandler协议,必须实现如下方法,然后把方法向外传递

//通过接收JS传出消息的name进行捕捉的回调方法

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

    if ([self.scriptDelegate respondsToSelector: @selector(userContentController:didReceiveScriptMessage:)]) {

        [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
        
    }
}

@end