阅读 66

iOS 的 WKWebView (Object-C)使用全面解析

WKWebView 是苹果爸爸在 iOS 8.0 公布的新一代用来展示网页交互内容的容器,基本出发点是全面替代原来的 UIWebView 。 因为 WKWebView 在加载效率和交互方面大大高出 UIWebView , 而且加上更便捷的使用方式,一经推出就得到了众多开发者的推崇。最近因为公司人员调配,和网页交互的任务落在了我这里,对WKWebView 经过学习了解, 总结一下其实际使用方式。

1 WKWebView 初始化

WKWebView 初始化包括如下知识点:

image

通过配置设定的 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 加载网页过程和页面交互过程中,加入自己的实现逻辑。代理包含的具体节点如下:

image
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

作为一个开发者,有一个学习的氛围和一个交流圈子特别重要,这是我的交流群[点击进群],大家有兴趣可以进群里一起交流学习


如果您觉得写得不错,请给我一个赞!

收录:[原文地址]


关注下面的标签,发现更多相似文章
评论