WKWebView 是苹果爸爸在 iOS 8.0 公布的新一代用来展示网页交互内容的容器,基本出发点是全面替代原来的 UIWebView 。 因为 WKWebView 在加载效率和交互方面大大高出 UIWebView , 而且加上更便捷的使用方式,一经推出就得到了众多开发者的推崇。最近因为公司人员调配,和网页交互的任务落在了我这里,对WKWebView 经过学习了解, 总结一下其实际使用方式。
1 WKWebView 初始化
WKWebView 初始化包括如下知识点:
通过配置设定的 WKUserContentController 可以注入 JS 方法,供 WKWebView 中加载的网页使用。WKPreferences 用来控制网页内容的基本属性,比如最小字体、是否允许运行 JS 代码等。
2 WKUserContentController 注入 JS 方法的方式
具体实现如下
WKUserContentController *userC = [[WKUserContentController alloc] init];
[userC addScriptMessageHandler:self name:@"showMsg"];
[userC addScriptMessageHandler:self name:@"selectPicture"];
[userC addScriptMessageHandler:self name:@"postClick"];
WKPreferences *preference = [WKPreferences new];
preference.minimumFontSize = 10;
preference.javaScriptCanOpenWindowsAutomatically = true;
WKWebViewConfiguration *config = [WKWebViewConfiguration new];
config.userContentController = userC;
config.preferences = preference;
self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, _progressView.frame.size.height, SCRREN_WIDTH, self.view.frame.size.height - _progressView.frame.size.height) configuration:config];
3 WKWebView 的代理解析
我们知道 iOS 中的代理就是 iOS 操作系统将运行周期节点暴露给开发者进行使用,通过 WKWebView 中两大代理 UIDelegate 和 navigationDelegate ,我们能够在 WKWebView 加载网页过程和页面交互过程中,加入自己的实现逻辑。代理包含的具体节点如下:
4 WKWebView 的代理方法实现
各具体代理方法的使用方式如下
#pragma mark - WKNavigationDelegate 页面跳转和载入
#pragma mark - 页面跳转
//收到跳转动作时, 决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSURLRequest *request = navigationAction.request;
NSString *hostname = request.URL.host.lowercaseString;
if (navigationAction.navigationType == WKNavigationTypeLinkActivated
&& ![hostname containsString:@"baidu.com"]) {
// 对于跨域,需要手动跳转
// [[UIApplication sharedApplication] openURL:navigationAction.request.URL];
// 不允许web内跳转
decisionHandler(WKNavigationActionPolicyCancel);
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
NSLog(@"收到跳转动作时, 决定是否跳转");
}
//收到服务器响应时, 决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
decisionHandler(WKNavigationResponsePolicyAllow);
NSLog(@"收到服务器响应时, 决定是否跳转");
}
//收到服务器跳转动作时, 决定是否跳转
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
NSLog(@"收到服务器跳转动作时, 决定是否跳转");
}
#pragma mark - WKNavigationDelegate 页面载入
// 开始请求内容
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
NSLog(@"开始请求内容");
}
// 开始请求内容时失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
NSLog(@"开始请求内容时失败");
}
// 服务器开始返回内容
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{
NSLog(@"服务器开始返回内容");
}
// 服务器开始返回内容完毕
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
NSLog(@"服务器开始返回内容完毕");
_progressView.hidden = true;
}
// 服务器开始返回内容过程错误
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
NSLog(@" 服务器开始返回内容过程错误");
_progressView.hidden = true;
}
// 页面权限变化
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
NSLog(@"页面权限变化时处理");
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
// 服务器开始返回内容过程终止
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
NSLog(@"服务器开始返回内容过程终止");
}
5 WKWebView 和 JS 的交互
WKWebView 作为容器,加载的网页内容,在响应页面交互时,很多时候需要调用 Native 的方法。比如,点击某个按钮选择相册和分享,然后退出包含 WKWebView 的 Controller 等。WKWebView 和 JS 的交互实现方式,逻辑上就是通过两者相互协商好的方法名和参数进行相互调用。
首先看一下 HTML 中 JS 的方法,包括 shareClick、shareResult、postClick、cameraClick 和 cameraResult:
<!DOCTYPE html>
<html >
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
<head>
<title>Test</title>
<style>
a{
// <!-- text-decoration:none; /* 去除a标签自带下划线 */-->
// <!-- border:1px solid #999;-->
//<!-- background-color: #F0F0F0;-->
//<!-- float:left; /* 设置浮动 */-->
//color:blue;
text-align:center;
margin:2px 5px;
width:100px;
height:20px;
font-size:80px;
}
input{
width:600px;
height:80px;
font-size:30px;
}
button{
width:200px;
height:100px;
font-size:30px;
}
textarea{
font-size:20px;
width:100%;
}
</style>
<script>
function shareClick() { window.webkit.messageHandlers.showMsg.postMessage({title:'uniapp',content:'一切从零开始',url:'https://github.com/uniapp10'});
}
//分享回调结果显示
function shareResult(channel_id,share_channel,share_url) {
var content = channel_id+","+share_channel+","+share_url;
alert(content);
document.getElementById("returnValue").value = content;
}
function postClick() {
var string = document.getElementById("textValue").value;
window.webkit.messageHandlers.postClick.postMessage(string);
}
//JS执行window.webkit.messageHandlers.Camera.postMessage(<messageBody>)
function cameraClick() {
window.webkit.messageHandlers.selectPicture.postMessage(null);
}
//调用相册回调结果显示
function cameraResult(result) {
alert(result);
document.getElementById("returnValue").value = result;
}
</script>
</head>
<body>
<a href="http://www.baidu.com" >跳转</a>
<div>
<form action="#" method="post">
<input type="search" placeholder="Quick Search">
<button type="submit"><span></span>提交表单</button>
</form>
</div>
<div>
<input type="text" placeholder="输入内容" id ="textValue">
<button type="submit" onclick="postClick()"><span></span>传递给OC</button>
</div>
<div>
<div>
<input type="button" value="分享" onclick="shareClick()" />
</div>
<div>
<input type="button" value="相机" onclick="cameraClick()" />
</div>
<div>
<h1>回调展示区</h1>
<textarea id ="returnValue" type="value" rows="5">
</textarea>
</div>
</div>
</body>
</html>
5.1 WKWebView 调用 JS
WKWebView 调用 JS 十分简单,直接通过字符串寻找 JS 中的方法名。比如 JS 中包含分享结果的方法:
//分享回调结果显示
function shareResult(channel_id,share_channel,share_url) {
var content = channel_id+","+share_channel+","+share_url;
alert(content);
document.getElementById("returnValue").value = content;
}
在 OC 中直接调用:
NSString *JSResult = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
//OC调用JS
[self.webView evaluateJavaScript:JSResult completionHandler:^(id _Nullable result, NSError * _Nullable error) {
if (error) {
NSLog(@"%@", error);
}else{
NSLog(@"%s", __FUNCTION__);
}
}];
5.2 JS 调用 WKWebView
JS 调用 WKWebView ,首先需要在 WKWebView 中注入 JS 方法,通过 WKUserContentController 可以实现。比如上面 2 中提前注入了 showMsg 、selectPicture 和 postClick 3 个方法,在 JS 中就可以通过方法名进行调用:
function shareClick() { window.webkit.messageHandlers.showMsg.postMessage({title:'uniapp',content:'一切从零开始',url:'https://github.com/uniapp10'});
}
function postClick() {
var string = document.getElementById("textValue").value;
window.webkit.messageHandlers.postClick.postMessage(string);
}
//JS执行window.webkit.messageHandlers.Camera.postMessage(<messageBody>)
function cameraClick() {
window.webkit.messageHandlers.selectPicture.postMessage(null);
}
WKWebView 中处理 JS 中调用的方法,是在 WKUserContentController 中的代理方法中, 通过 WKScriptMessage 类进行区分。其中 name 表示方法名, body 表示js 中传递的方法。
#pragma mark - WKScriptMessageHandler js 调用 OC 接口
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"showMsg"]) {
[self showMsg:message.body];
} else if ([message.name isEqualToString:@"selectPicture"]) {
[self selectPicture];
}else if ([message.name isEqualToString:@"postClick"]) {
[self postClick:message.body];
}
}
5.3 WKWebView 和 JS 的交互的注意点
当实现了 WKWebView 的 UIDelegate 方法后,JS 中使用 Alert 方法的弹出框,是不会显示在界面上的,需要通过 UIDelegate 的代理方法进行 Native 处理:
#pragma mark - WKUIDelegate 页面是否显示警告框\确认框\输入框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:[NSString stringWithFormat:@"js 调用 alert : %@",message] preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}]];
[self presentViewController:alert animated:YES completion:NULL];
NSLog(@"%@", message);
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"confirm" message:@"js 调用 confirm" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}]];
[self presentViewController:alert animated:YES completion:NULL];
NSLog(@"%@", message);
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"textinput" message:@"js 调用输入框" preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.textColor = [UIColor redColor];
}];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler([[alert.textFields lastObject] text]);
}]];
[self presentViewController:alert animated:YES completion:NULL];
}
6 WKWebView 进度条
在 WKWebView 加载过程中,通常情况下都需要告知用户加载进度,提高用户的等待耐心。通过监听其 estimatedProgress
属性配合 iOS 中的 UIProgressView 能够快捷实现。
6.1 WKWebView 注册监听
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
6.2 WKWebView 监听处理:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([keyPath isEqualToString:@"estimatedProgress"]) {
_progressView.progress = _webView.estimatedProgress;
}
else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
6.3 最后注意移除监听
- (void)dealloc{
[self.webView removeObserver:self forKeyPath:@"title" context:NULL];
}
7 其他
WKWebView 中 JS 和 Native 的交互方法都是异步调用,如果想要实现同步效果,网上介绍的方式是在 JS 中添加代码进行 UI 线程阻塞,个人不太赞成这种方法,完全可以通过 JS 调用 Native 方法处理,待 Native 方法处理完毕,主动调用 JS 中方法进行处理即可。
使用过程中,有其他疑问点儿,欢迎留言交流~
最后,具体项目 Demo