您的位置:首页 > 移动开发

WKWebView使用及注意点(keng)

2016-11-25 09:02 190 查看
iOS8
之后,苹果推出了
WebKit
这个框架,用来替换原有的
UIWebView
,新的控件优点多多,不一一叙述。由于一直在适配
iOS7
,就没有去替换,现在仍掉了
iOS7
,以为很简单的就替换过来了,然而在替换的过程中,却遇到了很多坑。还有一点就是原来写过一篇文章
Objective-C与JavaScript交互的那些事以为年代久远的
UIWebView
已经作古,可这篇文章现在依然有一定的阅读量。所以在决定在续一篇此文,以引导大家转向
WKWebView
,并指出自己踩过的坑,让大家少走弯路。

此篇文章的逻辑图



此篇文章的逻辑图

WKWebView使用

WKWebView简单介绍

使用及注意点
WKWebView
只能用代码创建,而且自身就支持了右滑返回手势
allowsBackForwardNavigationGestures
和加载进度
estimatedProgress
等一些
UIWebView
不具备却非常好用的属性。在创建的时候,指定初始化方法中要求传入一个
WKWebViewConfiguration
对象,一般我们使用默认配置就好,但是有些地方是要根据自己的情况去做更改。比如,配置中的
allowsInlineMediaPlayback
这个属性,默认为
NO
,如果不做更改,网页中内嵌的视频就无法正常播放。

更改User-Agent
有时我们需要在
User-Agent
添加一些额外的信息,这时就要更改默认的
User-Agent
在使用
UIWebView
的时候,可用如下代码(在使用
UIWebView
之前执行)全局更改
User-Agent


// 获取默认User-Agent
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString *oldAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

// 给User-Agent添加额外的信息
NSString *newAgent = [NSString stringWithFormat:@"%@;%@", oldAgent, @"extra_user_agent"];

// 设置global User-Agent
NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];

以上代码是全局更改
User-Agent
,也就是说,
App
内所有的
Web
请求的
User-Agent
都被修改。替换为
WKWebView
后更改全局
User-Agent
可以继续使用上面的一段代码,或者改为用
WKWebView
获取默认的
User-Agent
,代码如下:

self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectZero];

// 获取默认User-Agent
[self.wkWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
NSString *oldAgent = result;

// 给User-Agent添加额外的信息
NSString *newAgent = [NSString stringWithFormat:@"%@;%@", oldAgent, @"extra_user_agent"];

// 设置global User-Agent
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
}];

对比发现,这两种方法并没有本质的区别,一点小区别在于一个是用
UIWebView
获取的默认
User-Agent
,一个是用
WKWebView
获取的默认
User-Agent
。上面方法的缺点也是很明显的,就是
App
内所有
Web
请求的
User-Agent
全部被修改。

iOS9
WKWebView
提供了一个非常便捷的属性去更改
User-Agent
,就是
customUserAgent
属性。这样使用起来不仅方便,也不会全局更改
User-Agent
,可惜的是
iOS9
才有,如果适配
iOS8
,还是要使用上面的方法。

WKWebView的相关的代理方法
WKWebView
的相关的代理方法分别在
WKNavigationDelegate
WKUIDelegate
以及
WKScriptMessageHandler
这个与
JavaScript
交互相关的代理方法。

WKNavigationDelegate
: 此代理方法中除了原有的
UIWebView
的四个代理方法,还增加了其他的一些方法,具体可参考我下面给出的
Demo

WKUIDelegate
: 此代理方法在使用中最好实现,否则遇到网页
alert
的时候,如果此代理方法没有实现,则不会出现弹框提示。
WKScriptMessageHandler
: 此代理方法就是和
JavaScript
交互相关,具体介绍参考下面的专门讲解。

WKWebView使用过程中的坑

WKWebView下面添加自定义View
因为我们有个需求是在网页下面在添加一个
View
,用来展示此链接内容的相关评论。在使用
UIWebView
的时候,做法非常简单粗暴,在
UIWebView
ScrollView
后面添加一个自定义
View
,然后根据
View
的高度,在改变一下
scrollView
contentSize
属性。以为
WKWebView
也可以这样简单粗暴的去搞一下,结果却并不是这样。

首先改变
WKWebView
scrollView
contentSize
属性,系统会在下一次帧率刷新的时候,再给你改变回原有的,这样这条路就行不通了。我马上想到了另一个办法,改变
scrollView
contentInset
这个系统倒不会在变化回原来的,自以为完事大吉。后来过了两天,发现有些页面的部分区域的点击事件无法响应,百思不得其解,最后想到可能是设置的
contentInset
对其有了影响,事实上正是如此。查来查去,最后找到了一个解决办法是,就是当页面加载完成时,在网页下面拼一个空白的
div
,高度就是你添加的
View
的高度,让网页多出一个空白区域,自定义的
View
就添加在这个空白的区域上面。这样就完美解决了此问题。具体可参考
Demo
所写,核心代码如下:

self.addView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, addViewHeight)];
self.addView.backgroundColor = [UIColor redColor];
[self.webView.scrollView addSubview:self.addView];

NSString *js = [NSString stringWithFormat:@"\
var appendDiv = document.getElementById(\"AppAppendDIV\");\
if (appendDiv) {\
appendDiv.style.height = %@+\"px\";\
} else {\
var appendDiv = document.createElement(\"div\");\
appendDiv.setAttribute(\"id\",\"AppAppendDIV\");\
appendDiv.style.width=%@+\"px\";\
appendDiv.style.height=%@+\"px\";\
document.body.appendChild(appendDiv);\
}\
", @(addViewHeight), @(self.webView.scrollView.contentSize.width), @(addViewHeight)];

[self.webView evaluateJavaScript:js completionHandler:nil];

WKWebView加载HTTPS的链接
HTTPS
已经越来越被重视,前面我也写过一系列的
HTTPS
的相关文章HTTPS从原理到应用(四):iOS中HTTPS实际使用当加载一些
HTTPS
的页面的时候,如果此网站使用的根证书已经内置到了手机中这些
HTTPS
的链接可以正常的通过验证并正常加载。但是如果使用的证书(一般为自建证书)的根证书并没有内置到手机中,这时是链接是无法正常加载的,必须要做一个权限认证。开始在
UIWebView
的时候,是把请求存储下来然后使用
NSURLConnection
去重新发起请求,然后走
NSURLConnection
的权限认证通道,认证通过后,在使用
UIWebView
去加载这个请求。

WKWebView
中,
WKNavigationDelegate
中提供了一个权限认证的代理方法,这是权限认证更为便捷。代理方法如下:

- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([challenge previousFailureCount] == 0) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}

这个方法比原来
UIWebView
的认证简单的多。但是使用中却发现了一个很蛋疼的问题,
iOS8
系统下,自建证书的
HTTPS
链接,不调用此代理方法。查来查去,原来是一个
bug
,在
iOS9
中已经修复,这明显就是不管
iOS8
的情况了,而且此方法也没有标记在
iOS9
中使用,这点让我感到有点失望。这样我就又想到了换回原来
UIWebView
的权限认证方式,但是试来试去,发现也不能使用了。所以关于自建证书的
HTTPS
链接在
iOS8
下面使用
WKWebView
加载,我没有找到很好的办法去解决此问题。这样我不得已有些链接换回了
HTTP
,或者在
iOS8
下面在换回
UIWebView
。如果你有解决办法,也欢迎私信我,感激不尽。

WKWebView加载POST请求
非常感谢@e231e1ff5f8b的指出,原来
POST
请求这儿还有一个坑。自己项目中并没有这块需求,也就没有发现。加载
POST
请求的时候,会丢失
HTTPBody
。解决办法是在网页上开一个
JavaScript
方法,在请求
POST
的时候去调用
JavaScript
这个方法,从而完成
POST
请求。调用
JavaScript
方法参考下面交互这一章节。

WKWebView和JavaScript交互

WKWebView
JavaScript
交互,在
WKUserContentController.h
这个头文件中
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
这个方法的注释中已经明确给出了交互办法。使用起来倒是非常的简单。创建
WKWebView
的时候添加交互对象,并让交互对象实现
WKScriptMessageHandler
中的唯一的一个代理方法。具体的方式参考Demo中的使用。

// 添加交互对象
[config.userContentController addScriptMessageHandler:(id)self.ocjsHelper name:@"timefor"];

// 代理方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

JavaScript
调用
Objective-C
的时候,使用
window.webkit.messageHandlers.timefor.postMessage({code: '0001', functionName: 'getdevideId'}); Objective-C
自动对交互参数包装成了
WKScriptMessage
对象,其属性
body
则为传送过来的参数,
name
为添加交互对象的时候设置的名字,以此名字可以过滤掉不属于自己的交互方法。其中
body
可以为
NSNumber,
NSString, NSDate, NSArray, NSDictionary, and NSNull。


Objective-C
在回调
JavaScript
的时候,不能像我原来在
Objective-C与JavaScript交互的那些事这篇文章中写的那样,
JavaScript
传过来一个匿名函数,
Objective-C
这边直接调用一下就完事。
WKWebView
没有办法传过来一个匿名函数,所以回调方式,要么执行一段
JavaScript
代码,或者就是调用
JavaScript
那边的一个全局函数。一般是采用后者,至于
Web
端虽说暴露了一个全局函数,同样可以把这一点代码处理的很优雅。
Objective-C
传给
JavaScript
的参数,可以为
Number,
String, and Object
。参考如下:

// 数字
NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", number];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 字符串
NSString *js = [NSString stringWithFormat:@"globalCallback(\'%@\')", string];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 对象
NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", @{@"name" : @"timefor"}];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 带返回值的JS函数
[self.webView evaluateJavaScript:@"globalCallback()" completionHandler:^(id result, NSError * _Nullable error) {
// 接受返回的参数,result中
}];

总结

此文主要介绍了
WKWebView
使用中的注意点,一般也都是常用的,还有缓存等一些不是太常用的就没有具体介绍。如果在其他方面遇到问题,也欢迎你私信我共同探讨进步。
WKWebView
确实比
UIWebView
有些地方好用不少,但是一些
bug
至今也没解决,权限挑战是在
iOS9
解决的,
POST
请求则至今没有解决,而改变
contentInset
导致的点击事件不准确,同样是没有解决。这些问题让开发者使用起来,有诸多不便啊。

此文的Demo地址:WKWebViewDemo 如果此文对你有所帮助,请给个
star
吧。

参考

http://stackoverflow.com/questions/34693311/links-in-wkwebview-randomly-not-clickable/35100064#35100064
https://bugs.webkit.org/show_bug.cgi?id=140197
http://stackoverflow.com/questions/26253133/cant-set-headers-on-my-wkwebview-post-request

文/TIME_for(简书作者)

原文链接:http://www.jianshu.com/p/9513d101e582

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: