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

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

推荐阅读更多精彩内容