前言
WKWebview加载淘宝首页使用内存为45M;而UIWebview相同条件下加载淘宝首页使用的内存为150M,从这就可以直观地知道,WKWebview的推出,性能上确实是比UIWebView强那么一点,所以站长认为,以后,项目使用WebView的时候首先考虑采用WKWebview。
在使用UIWebView的时候,我们建议通过JavaScriptCore来实现OC和JS的交互。但是,在使用MKWebView的时候,因为布局和JavaScript是在另一个进程上处理的,我们没有办法从MKWebView中获取到jsContext,我们不能使用JavaScriptCore来实现MKWebView中的OC和JS的交互!
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; }