初识WKWebView

公司开始让做一个新iOS项目,由于苹果的更新需要每次发版本审核,没法像服务器一样实时更新,技术部就讨论出原生+HTML混合编程的策略。一想到混合编程就毫不犹豫的开始用UIWebView,但是发现点击按钮十分卡顿,用户体验不是很好。想起苹果在2014年新出的WKWebView,想试下效果如何,结果让我很满意,心里甚是欢喜。但是.......

调用HTML必然牵扯到交互,以前在网上找的很好用的第三方框架(https://github.com/dukeland/EasyJSWebView)不适用WKWebView,刚开始还自以为是的想着把原来的UIWebView的代理方法替换成对应的WKWebView的代理方法就行了,就埋头在那里替换,替换完之后发现不行,就开始查看WKWebView的官方文档,还是我的功力太浅,看的一知半解,也不知道应该怎么去做,就只好去网上查找优秀的文章。(我想说的是,我解决问题的思路是有问题的,有点想当然的去做,或许应该先去了解WKWebView,对比着WKWebView和UIWebView的优劣,然后决定用哪个,而不是先做发现问题再去想办法解决,幸亏现在是把那些坑填满了。。。如果一开始的选择是个无底洞,不知道又要做多少无用功了)。以下内容就按照我遇到问题的顺序,对WKWebView做一总结:

一  、WKWebView简介

WKWebView是现代 WebKit API 在 iOS 8 和 OS X Yosemite 应用中的核心部分。它代替了 UIKit 中的UIWebView和 AppKit 中的WebView,提供了统一的跨双平台 API(iOS和OS)。

二、WKWebView新特性

在性能、稳定性、功能方面有很大提升(最直观的体现就是加载网页是占用的内存,模拟器加载百度与开源中国网站时,WKWebView占用23M,而UIWebView占用85M);

和 Safari 相同的 JavaScript 引擎,允许JavaScript的Nitro库加载并使用(UIWebView中限制);

支持了更多的HTML5特性;

自诩拥有 60fps 滚动刷新率、内置手势、高效的 app 和 web 信息交换通道

将UIWebViewDelegate与UIWebView重构成了14类与3个协议(具体查看苹果官方文档);

三、WKWebView不足


WKWebView会忽视默认的网络存储, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。WKWebView有自己的进程,同样也有自己的存储空间用来存储cookie和cache, 其他的网络类如NSURLConnection是无法访问到的。同时WKWebView发起的资源请求也是不经过NSURLProtocol的,导致无法自定义请求。同一个应用,不同WKWebView之间的cookie同步,可以通过使用同一个WKProcessPool实现cookie的同步。(我没有具体实现,如果想尝试,可以参考http://mcfeng.me/blog/2014/11/03/wkwebview-uiwebview-cookie-sync/)

简单来说就是页面之间cookie同步和共享,以前同一个应用,不同UIWebView之间的Cookie是自动同步的。它们都是保存在NSHTTPCookieStorage中。当UIWebView加载一个URL的时候,在加载完成时候,这个URL response中包含的cookie会自动以NSHttpCookie的形式保存到NSHTTPCookieStorage中。同时,如果在http response中,对cookie进行更新或者删除的话,其结果也会直接反应到NSHTTPCookieStorage存储的cookie数据中。所以,如果需要取出cookie数值,只要调用[NSHTTPCookieStorage sharedHTTPCookieStorage]即可。但是现在需要手动的设置cookie来保持页面之间信息同步。

四、WKWebView与HTML交互

WKWebView调用js脚本(以下为示例代码,作为参考)

```

#pragma mark - WKNavigationDelegate

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{

NSString *executeString = @"$(document).ready(function(){$('.guanliansearch').removeClass(\"hide\").addClass(\"show\") });";

[self.webView evaluateJavaScript:executeString completionHandler:^(id _Nullable result, NSError * _Nullable error) {

}];

}

```

HTML调用OC方法

```
UIViewController<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler>中WKWebView的初始化,注意要遵守协议

- (void)viewDidLoad {

[super viewDidLoad];

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];

[configuration.userContentController addScriptMessageHandler:self name:@"goBack"];(goBack为方法名,与html中对应)

self.webView = [[WKWebView alloc] initWithFrame:viewRect configuration:configuration];

self.webView.navigationDelegate = self;

self.webView.UIDelegate = self;

self.webView.customUserAgent = kCustomUserAgent;


//让HTML适配屏幕

self.edgesForExtendedLayout = UIRectEdgeNone;

self.automaticallyAdjustsScrollViewInsets = NO;

[self.view addSubview:self.webView];

//显示加载页

[self showLoadingView];

// 1. URL 定位资源,需要资源的地址

NSURL *url = [NSURL URLWithString:self.webViewUrl];

// 2. 把URL告诉给服务器,请求,从m.baidu.com请求数据

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

// 设置缓存策略

request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

// 3. 发送请求给服务器

[self.webView loadRequest:request];

}```

```

#pragma mark - WKScriptMessageHandler

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

NSLog(@"%@",NSStringFromSelector(_cmd));

NSLog(@"message.body--%@  message.name--%@",message.body,message.name);

NSArray *messageArray = (NSArray *)message.body;(如果请求为多个参数,强转为数组)

if ([message.name isEqualToString:@"goBack"]){

[self  goBack];

}

}

- (void)goBack{

[self dismissViewControllerAnimated:YES completion:^{

}];

}```

HTML对应的配置

```

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<script type="text/javascript">//JS响应方法列表>

function btnClick1() {

var useragent = navigator.userAgent;

if(useragent == “***”){

window.webkit.messageHandlers.goBack.postMessage(null);(没有参数的话必须写null,如果不写按钮点击也没有反应,如果多个参数写法( ['param1','param2'])  ,即用数组格式;一个参数格式(‘param1’),goBack为方法名)

}

}</script>

<style type="text/css">body {background-color:white;font-size:20px;padding:10;}</style>

li {padding:5}

button { width:700; height:100;margin:10}

</head>

<body><ul><li><button type="button" onclick="btnClick1()">showToast></button></li></ul></body>

</html>

```

五,WKWebView  的WKUIDelegate方法的实现


```

#pragma mark - WKUIDelegate(我只是实现了2个作为参考,3个方法分别对应JavaScript 中创建三种消息框:警告框、确认框、提示框,如果不实现相应的方法,则HTML中写的消息框无反应)

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{

WBLog(@"webView.URL---%@",webView.URL);

UIAlertController *alertController1 = [UIAlertController alertControllerWithTitle:message message: nil preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction *noAction  = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

completionHandler();(该方法一定要实现,否则会系统会崩溃,错误日志很奇葩)

}];

[alertController1  addAction:noAction];

[self presentViewController:alertController1  animated:YES completion:^{

}];

}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message message: nil preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction *cancelAction  = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

completionHandler(WKNavigationResponsePolicyCancel);

}];

[alertController  addAction:cancelAction];

[self presentViewController:alertController  animated:YES completion:^{

}];

UIAlertAction *okAction  = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

completionHandler(WKNavigationResponsePolicyAllow);

}];

[alertController  addAction:okAction];

}

```

五,WKWebView  的cookie共享


说道cookie肯定会想到session,首先简单了解下:(参考资料:http://blog.csdn.net/fangaoxin/article/details/6952954/)

会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份Session通过在服务器端记录信息确定用户身份

Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。

由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

以下是我手动设置cookie的代码,(希望有更好的方式,可以多多指教),思路是,当用户登录完成之后,保存页面返回的cookie,主要三个属性,JSESSIONID,uid,token
用户登录完成之后,每次访问页面都携带有这3个属性的cookie

```

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{

NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;

NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];

for (NSHTTPCookie *cookie in cookies) {

//每次都存最新的sessionId

[SettingBaseTool deleteDatasByKey:[cookie name]];

[SettingBaseTool saveBaseInfoStringWithKey:[cookie name] value:[cookie value] isDelayUserId:NO];

}

//跳转之前传入cookie

//js函数

NSString *JSFuncString =

@"function setCookie(name,value,expires)\

{\

var oDate=new Date();\

oDate.setDate(oDate.getDate()+expires);\

document.cookie=name+'='+value+';expires='+oDate;\

}\

function getCookie(name)\

{\

var arr = document.cookie.match(new RegExp('(^| )'+name+'=([^;]*)(;|$)'));\

if(arr != null) return unescape(arr[2]); return null;\

}\

function delCookie(name)\

{\

var exp = new Date();\

exp.setTime(exp.getTime() - 1);\

var cval=getCookie(name);\

if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\

}";

//拼凑js字符串

//取出 JSESSIONID  uid token

NSString *jessionString = @"JSESSIONID";

NSString *uidString = @"uid";

NSString *tokenString = @"token";

NSMutableString *JSCookieString = JSFuncString.mutableCopy;

NSString *jessionValueString = [SettingBaseTool readBaseInfoStringWithKey:jessionString isDelayUserId:NO];(从我自己封装的方法从沙盒中取出值)

if ([jessionValueString isNotEmpty]) {

NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", jessionString, jessionValueString];

[JSCookieString appendString:excuteJSString];

}

NSString *uidValueString = [SettingBaseTool readBaseInfoStringWithKey:uidString isDelayUserId:NO];

if ([uidValueString isNotEmpty]) {

NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", uidString, uidValueString];

[JSCookieString appendString:excuteJSString];

}

NSString  *tokenVauleString = [SettingBaseTool readBaseInfoStringWithKey:tokenString isDelayUserId:NO];

if ([tokenVauleString isNotEmpty]) {

NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", tokenString, tokenVauleString];

[JSCookieString appendString:excuteJSString];

}

//执行js

[webView evaluateJavaScript:JSCookieString completionHandler:^(id _Nullable result, NSError * _Nullable error) {

decisionHandler(WKNavigationResponsePolicyAllow);

}];

}


#pragma mark - WKNavigationDelegate

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{

NSString *str = @"document.cookie";(获取页面返回的cookie,然后设置更新)

[webView evaluateJavaScript:str completionHandler:^(id _Nullable result, NSError * _Nullable error) {

if ([result isNotEmpty]) {

NSArray *resultArray  =[result componentsSeparatedByString:@";"];

if (resultArray.count > 0) {

for (NSString *resultString in resultArray) {

NSArray *keyValueArray = [resultString componentsSeparatedByString:@"="];

if (keyValueArray.count > 1) {

[SettingBaseTool updateDatasByKey:[keyValueArray[0] stringByReplacingOccurrencesOfString:@" " withString:@""] value:keyValueArray[1] ];(自己封装的方法,更新三个属性)

}

}

}

}

}];

```

六,捕获<a href:"9998989">

//在发送请求之前,决定是否跳转  如果不实现这个代理方法,默认会屏蔽掉打电话等url

```

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

// 允许跳转

decisionHandler(WKNavigationActionPolicyAllow);

}

//页面开始加载

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{

NSString *path=[webView.URL absoluteString];

NSString * newPath = [path lowercaseString];

//捕获URL,然后用UIApplication打开

if ([newPath hasPrefix:@"sms:"] || [newPath hasPrefix:@"tel:"] || [newPath hasPrefix:@"apipays:"] || [newPath hasPrefix:@"mqqwpa:"]) {

UIApplication * app = [UIApplication sharedApplication];

if ([app canOpenURL:[NSURL URLWithString:newPath]]) {

[app openURL:[NSURL URLWithString:newPath]];

}

return;

}

}

```

七,调用第三方,QQ,或者支付宝

点击QQ和支付宝都没有反应,思考原因,最后发现由于自定义了

self.webView.customUserAgent  = @“hah-ios”而第三方判断是浏览器还是Android或者iPhone的标准是基于默认值,所以就改成如下

```

[self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {

NSString *defaultAgent = (NSString *)result;

self.webView.customUserAgent = [NSString stringWithFormat:@"%@ hah-ios",defaultAgent];

}];

```

当然代码有些冗余,有待优化。不过有种拨开乌云见明月的感觉。。顿时感觉压力小了很多。。。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容