hybrid项目—–WKWebView和JS交互

前言

WKWebview加载淘宝首页使用内存为45M;而UIWebview相同条件下加载淘宝首页使用的内存为150M,从这就可以直观地知道,WKWebview的推出,性能上确实是比UIWebView强那么一点,所以站长认为,以后,项目使用WebView的时候首先考虑采用WKWebview。

在使用UIWebView的时候,我们建议通过JavaScriptCore来实现OC和JS的交互。但是,在使用MKWebView的时候,因为布局和JavaScript是在另一个进程上处理的,我们没有办法从MKWebView中获取到jsContext,我们不能使用JavaScriptCore来实现MKWebView中的OC和JS的交互!

UIWebView的JS交互请查看本站另一篇文章:

Hybrid项目—–UIWebView和JS交互

这里主要介绍,在MKWebView,我们实现JS和OC交互的两种方式。

本文的学习过程

OC调用JS

WKWebView有一个调用JS代码的方法

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

使用时如下:

[_webview evaluateJavaScript:@"testevent('12')" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@", error);
    }];

其中testevent(’12’)是方法名和参数!!

这些方法名和参数有两种来源: 1. 前端JS写入。 2. OC项目注入。

JS调用OC(两种方式)

1.方式一:直接注入脚本代码

2.方式二:添加handler,提供JS调用

这两种方式都是通过WKWebView的WKWebViewConfiguration对象的属性里的WKUserContentController对象。

官方描述:可以抽象WKUserContentControlle是关联web视图的用户内容控制器。

首先,我们给项目铺上WKWebView

1.懒加载初始化一个WKWebViewConfiguration对象

- (WKWebViewConfiguration *)configer{
    if (!_configer) {
        _configer = [[WKWebViewConfiguration alloc] init];
        _configer.userContentController = [[WKUserContentController alloc] init];
        _configer.preferences = [[WKPreferences alloc] init];
        _configer.preferences.javaScriptEnabled = YES;
        _configer.preferences.javaScriptCanOpenWindowsAutomatically = NO;
        _configer.allowsInlineMediaPlayback = YES;
    }
    return _configer;
}

指定WKWebView的configuration并将WebView铺上。。。

 _webview = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:self.configer];
[_webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http:/localhost:8086/JSTest"]]];
_webview.navigationDelegate = self;
_webview.UIDelegate = self;

1.方式一:直接注入脚本代码

这种方式,需要OC编程人员有Javascript知识,而前端调用时,比较直接。

WK的WKUserContentController对象有一个直接注入JS的方法

- (void)addUserScript:(WKUserScript *)userScript;

下面我们使用这个方法进行: JS代码注入,JS方法注入,JS对象注入。。。

注意:注入代码必须在页面开始加载时调用前

JS代码注入

//JS代码
    NSString *javaScriptSource = @"alert(\"WKUserScript注入js\");";
    WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javaScriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];// forMainFrameOnly:NO(全局窗口),yes(只限主窗口)
    [self.configer.userContentController addUserScript:userScript];

这个alert注入后,页面加载完成后在设置了alertUI的情况下,会弹出警示框。

JS方法的注入

// JS方法的创建代码
  NSString * function1 = @"function ocfunction(){jsParamFunc({'name': '我是OC注入的方法', 'age': 10, 'height': 170});}";
  [self.configer.userContentController addUserScript:[[WKUserScript alloc] initWithSource:function1 injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]];

这个方法里面调用了JS写好的方法,这个方法本来JS标签里面是没有的!以这种方式注入后,html文件里就可以调用这个方法了!具体请查看Demo。。。

JS对象的注入

NSString *obj = @"var testobj = {\"name\":\"zhangsan\",\
    \"age\":30,\
    \"son\":[\
    {\
    \"name\":\"jack\",\
    \"age\":2\
    },{\
    \"name\":\"rose\",\
    \"age\":3\
    }\
    ],\
    \"info\": function(){\
    document.getElementById('result').innerHTML = document.getElementById('result').innerHTML + this.name + '<br/>'\
    return 'd';\
    }\
    };";
    [self.configer.userContentController addUserScript:[[WKUserScript alloc] initWithSource:obj injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]];

这里是JS对象的代码字符串,包装成WKUerScript对象,然后注入到webView里面去!

如果对JS不太了解,可以参考本文JS入门的对象创建文章,应该有帮助:

https://www.varsiri.com/archives/381

Html调用时候,可以直接调用对象的属性和方法,例如:

<input type="button" value="第一种直接注入JS对象调用(请写对象名字testobj)" onclick="testobj.info()"><br/><br/>

调用OC方法可以使用跳转方式实现

'callOCfunction':function(){
    window.location.href="lalal://aaa";
}

在OC项目里,对跳转URL进行拦截,实现JS调用OC代码的功能

// 导航拦截
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    NSLog(@"-------->%@",navigationAction.request);
    
    
    if ([navigationAction.request.URL.absoluteString containsString:@"local"]) {
         decisionHandler(WKNavigationActionPolicyAllow);
    } else {
        decisionHandler(WKNavigationActionPolicyCancel);
    }
   
    
}

可根据URL内容,确定是否允许跳转

2.方式二:添加handler,提供JS调用

这种方式,OC编程人员会比较喜欢,方法以name注册,回调实现被调用的方法! 但是,前端开发有限制,必须要用以下代码来动用方法:

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

例如:

JS端调用的代码

JS标签里写好一个调用OC的方法

function callOCfunction(param){
         eval("window.webkit.messageHandlers."+param+".postMessage({title:'测试分享的标题',content:'测试分享的内容',url:'www.baidu.com'})");
       }

前端按键调用方法

<input type="button" value="第二种直接注入对象调用(请写name名为varsiri)" onclick="callOCfunction('varsiri')"><br/><br/>

OC端注册方法,并且协议中实现方法

在OC端,我们要给messagehandler设置一个名字使用方法如下:

[self.configer.userContentController  addScriptMessageHandler:self name:@"varsiri"];

name:是message handler的名字,回调时需要用来区分handler。

实现<WKScriptMessageHandler>协议里面的协议方法

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    NSLog(@"方法名:%@", message.name);
    NSLog(@"参数:%@", message.body);
    // 方法名
    NSString *methods = [NSString stringWithFormat:@"%@:", message.name];
    SEL selector = NSSelectorFromString(methods);
    // 调用方法
    if ([self respondsToSelector:selector]) {
        [self performSelector:selector withObject:message.body];
    } else {
        NSLog(@"未实行方法:%@", methods);
    }
}

name:handler的name,对应addScriptMessageHandler的name。

body:js传过来的数据!!!

第二种方式调用说明完毕!!!

最后,有一点需要重点说明:

在WKWebView里,JS调用的alert confirm prompt都是需要完善UI对应的UIDelegate协议才会显示的!

例如,需要实现如下:

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{    // js 里面的alert实现,如果不实现,网页的alert函数无效
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message
                                                                             message:nil
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"确定"
                                                        style:UIAlertActionStyleCancel
                                                      handler:^(UIAlertAction *action) {
                                                          completionHandler();
                                                      }]];
    
    [self presentViewController:alertController animated:YES completion:^{}];
    
}


- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
    //  js 里面的alert实现,如果不实现,网页的alert函数无效  ,
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message
                                                                             message:nil
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"确定"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction *action) {
                                                          completionHandler(YES);
                                                      }]];
    [alertController addAction:[UIAlertAction actionWithTitle:@"取消"
                                                        style:UIAlertActionStyleCancel
                                                      handler:^(UIAlertAction *action){
                                                          completionHandler(NO);
                                                      }]];
    
    [self presentViewController:alertController animated:YES completion:^{}];
    
}

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
    completionHandler(@"我是OC代码");
    
}

总结

本文主要描述了在WKWebView中如何实现 OC调用JS代码,JS调用OC代码的两种方式,以及

OC需要实现UIDelegate方法!!

JS调用OC两种方式的优缺点:

1.第一种方式,缺点:需要OC编程人员直接将JS代码注入WebView,需要OC编程人员有JS编程知识。 优点:方法直接,前端开发人员调用方便。兼容性高,可兼容android端开发。

2.第二种方式:缺点:兼容性低,需要JS端固定代码调用,android端需要配合。优点:OC项目纯OC代码,使用方便!

记录:(给WKwebView设置cookie)

– (v

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    WKUserContentController* userContentController = WKUserContentController.new;
    WKUserScript * cookieScript = [[WKUserScript alloc]
        initWithSource: [self _getCookies]
        injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    // again, use stringWithFormat: in the above line to inject your values programmatically
    [userContentController addUserScript:cookieScript];
    self.configer.userContentController = userContentController;
    
    self.webview = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:self.configer];
    [self.webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[K_storagePreUrl stringByAppendingFormat:@"%@",self.s_id]]]];
    self.webview.navigationDelegate = self;
    [self.view addSubview:self.webview];
//    _webview.UIDelegate = self;
    [self _setNaviBack];
    
    if (@available(iOS 11.0, *)){

        for (NSHTTPCookie *cookie in [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies) {
            [self.webview.configuration.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
        }
       }
}


- (WKWebViewConfiguration *)configer{
    if (!_configer) {
        _configer = [[WKWebViewConfiguration alloc] init];
        _configer.userContentController = [[WKUserContentController alloc] init];
        _configer.preferences = [[WKPreferences alloc] init];
        _configer.preferences.javaScriptEnabled = YES;
        _configer.preferences.javaScriptCanOpenWindowsAutomatically = NO;
        _configer.allowsInlineMediaPlayback = YES;
    }
    return _configer;
}

- (NSString *)_getCookies{
    //防止Cookie丢失
    NSDictionary *dict = [NSHTTPCookie requestHeaderFieldsWithCookies:[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies];
    NSString *cookieStr = [dict valueForKey:@"Cookie"];
    return cookieStr;
}

发表评论

邮箱地址不会被公开。 必填项已用*标注