iOS开发WKWebView的使用

前言

一直以来项目中都习惯使用UIWebview,其实WK功能更加强大,包括与JS的交互。WK的加载速度比UIWebView提升差不多一倍,内存使用上面反而还少了一半。并且苹果推荐使用WK。所以,今天就整理一下WK的相关知识。

简介

iOS8.0之后,苹果推荐使用WebKit框架中的WKWebView来加载网页,使用WKWebViewConfiguration来配置JS交互。

使用

首先导入#import <WebKit/WebKit.h>
创建UI

    _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_W, SCREEN_H) configuration:config];  //config为创建好的配置对象
    _webView.UIDelegate = self;        // UI代理
    _webView.navigationDelegate = self;        // 导航代理
    _webView.allowsBackForwardNavigationGestures = YES;       // 左滑返回
 
   //可返回的页面列表, 存储已打开过的网页 
    WKBackForwardList * backForwardList = [_webView backForwardList];

常用方法

打开网页

    NSString *path = [[NSBundle mainBundle] pathForResource:@"JStoOC.html" ofType:nil];
    NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
      //加载本地html文件
    [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];

网页切换

    [_webView goBack];    //页面后退

    [_webView goForward];    //页面前进

    [_webView reload];    //刷新当前页面

网页执行JS方法

[_webView evaluateJavaScript:@"h5method()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
                              
}];

OC与JS的交互

  • js调用OC
    主要依靠WKScriptMessageHandler协议类、WKUserContentController其中:
    WKUserContentController对象负责注册JS方法,设置处理接收JS方法的代理,代理遵守WKScriptMessageHandler,实现捕捉到JS消息的回调方法。

1、配置与JS的交互
WKWebViewConfiguration来配置JS交互

    //1、创建网页配置对象
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        
    // 2、添加设置对象WKPreferences
    WKPreferences *preference = [[WKPreferences alloc]init];
    //2.1最小字体大小
    // 当将javaScriptEnabled属性设置为NO时,可以看到明显的效果
    preference.minimumFontSize = 0;
    //2.2是否支持javaScript 默认是支持的
    preference.javaScriptEnabled = YES;  
    //2.3是否允许不经过用户交互由javaScript自动打开窗口
    preference.javaScriptCanOpenWindowsAutomatically = YES;   // 默认为NO
    // 2.4添加  
    config.preferences = preference;
        
    //3、一般配置
    // 是使用h5的视频播放器在线播放, 还是使用原生播放器全屏播放
    config.allowsInlineMediaPlayback = YES;
    //设置视频是否需要用户手动播放,设置为NO则会允许自动播放
    config.requiresUserActionForMediaPlayback = YES;
    //设置是否允许画中画技术 (在特定设备上有效
    config.allowsPictureInPictureMediaPlayback = YES;
    //设置请求的User-Agent信息中应用程序名称( iOS9后可用
    config.applicationNameForUserAgent = @"ChinaDailyForiPad";

2、使用WKUserContentController,用来做 原生与JavaScript的交互管理

    WKUserContentController * wkUController = [[WKUserContentController alloc] init];    
    //注册一个name为 jsToOcNoPrams 的js方法
    [wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcNoPrams"];
    [wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcWithPrams"]; 

    // 设置 
    config.userContentController = wkUController;

    // 用完移除
    [[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcNoPrams"];
       [[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcWithPrams"];

3、使用协议类WKScriptMessageHandler,用来处理监听JavaScript方法从而调用原生OC方法。(和WKUserContentController搭配使用)

    // 创建代理
    WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate =
 [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];

4、通过 接收JS传出消息的name 进行捕捉的回调方法
ps:遵守WKScriptMessageHandler协议,代理是由WKUserContentControl设置

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

    NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
    
    //用message.body获得JS传出的参数体
    NSDictionary * parameter = message.body;

    //JS调用OC
    if([message.name isEqualToString:@"jsToOcNoPrams"]){
        
        NSLog("----->js调用到了oc , 不带参数");      
  
    }else if([message.name isEqualToString:@"jsToOcWithPrams"]){
         NSLog("----->js调用到了oc , 带参数");      
    }
}
  • OC调用js
    使用WKUserScript,执行自定义的JavaScript代码
    // js代码字符串
    NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    // 创建WKUserScript
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    //  设置
    [config.userContentController addUserScript:wkUScript];

动态加载并运行JS代码,用于在客户端内部加入JS代码,并执行,如:

// 图片缩放的js代码
NSString *js = @"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '张图');";
// 根据JS字符串初始化WKUserScript对象
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
// 根据生成的WKUserScript对象,初始化WKWebViewConfiguration
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:script];
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];

NSString *html = @"<head></head><imgea src='http://www.nsu.edu.cn/v/2014v3/img/background/3.jpg' />";
[_webView loadHTMLString:html baseURL:nil];
[self.view addSubview:_webView];

WKWebView涉及的代理方法

1、WKNavigationDelegate协议
主要处理一些跳转、加载处理操作

  • 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {

}
  • 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {

    [self.progressView setProgress:0.0f animated:NO];
}
  • 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
}
  • 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    // [self getCookie];
}
  • 提交发生错误时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {

    [self.progressView setProgress:0.0f animated:NO];
}
  • 接收到服务器跳转请求,即服务重定向时之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
}
  • 根据WebView对于即将跳转的HTTP请求头信息和相关信息
    来决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    
    NSString * urlStr = navigationAction.request.URL.absoluteString;

    NSString *htmlHeadString = @"github://";    //自己定义的协议头

    if([urlStr hasPrefix:htmlHeadString]){
    // 通过截取URL调用OC
    NSURL * url = [NSURL URLWithString:[urlStr stringByReplacingOccurrencesOfString:@"github://callName_?" withString:@""]];
            [[UIApplication sharedApplication] openURL:url];

        decisionHandler(WKNavigationActionPolicyCancel);  // 
    }else{
        decisionHandler(WKNavigationActionPolicyAllow);   // 允许直接跳转
    }
}
  • 根据客户端受到的服务器响应头,以及response相关信息,来决定是否可以跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSString * urlStr = navigationResponse.response.URL.absoluteString;
    NSLog(@"当前跳转地址:%@",urlStr);
    //允许跳转
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationResponsePolicyCancel);
}
  • 需要响应身份验证时调用 同样在block中需要传入用户身份凭证
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    //用户身份信息
    NSURLCredential * newCred = [[NSURLCredential alloc] initWithUser:@"user123" password:@"123" persistence:NSURLCredentialPersistenceNone];

    //为 challenge 的发送方提供 credential
    [challenge.sender useCredential:newCred forAuthenticationChallenge:challenge];
    completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}

2.WKUIDelegate协议
主要处理JS脚本,确认框,警告框等

  • web界面中有弹出警告框时调用
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {

     NSLog("HTML弹出框");
    completionHandler();
}

@param webView : 实现该代理的webview
@param message : 警告框中的内容
@param completionHandler : 警告框消失回调

  • 确认框。JavaScript调用confirm方法后回调的方法
    confirm是js中的确定框,需要在block中把用户选择的情况传递进去
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    completionHandler(NO);
}
  • 输入框。JavaScript调用prompt方法后回调的方法
    prompt是js中的输入框,需要在block中把用户输入的信息传入
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = defaultText;
    }];
    [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}
  • 页面是弹出窗口 _blank 处理
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}

网页内容加载进度条

0.添加进度条

// 属性
@property (nonatomic, strong) UIProgressView *progressView;

// 懒加载
- (UIProgressView *)progressView
{
    if (!_progressView)
    {
        _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_W, 2)];
        _progressView.backgroundColor = [UIColor blueColor];
        _progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
        _progressView.progressTintColor = [UIColor app_color_yellow_eab201];
        [self.view addSubview:self.progressView];
    }
    return _progressView;
}

1.添加观察者

//添加 监测网页加载进度 的观察者
    [self.webView addObserver:self
                   forKeyPath:@"estimatedProgress"
                      options:0
                      context:nil];

//添加 监测网页标题title 的观察者
    [self.webView addObserver:self
                   forKeyPath:@"title"
                      options:NSKeyValueObservingOptionNew
                      context:nil];

2.监听方法

//---kvo 监听进度 必须实现此方法
-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                      context:(void *)context
{
    if ([keyPath isEqualToString:@"estimatedProgress"])
    {
       self.progressView.progress = self.webView.estimatedProgress;
       if (self.progressView.progress == 1)
       {
           WeakSelfDeclare
           [UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^
           {
               weakSelf.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.4f);
           }
                            completion:^(BOOL finished)
           {
               weakSelf.progressView.hidden = YES;
           }];
       }
   }else if([keyPath isEqualToString:@"title"]
             && object == _webView){
        self.navigationItem.title = _webView.title;
    }else{
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

3.显示/隐藏进度条

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
       self.progressView.hidden = NO;
       self.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
       [self.view bringSubviewToFront:self.progressView];
}

 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
       self.progressView.hidden = YES;
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
       if(error.code==NSURLErrorCancelled)
       {
           [self webView:webView didFinishNavigation:navigation];
       }
       else
       {
           self.progressView.hidden = YES;
       }
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
       self.progressView.hidden = YES;
       [self.navigationItem setTitleWithCustomLabel:@"加载失败"];
}

4.移除观察者

    [_webView removeObserver:self
                  forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
    [_webView removeObserver:self
                  forKeyPath:NSStringFromSelector(@selector(title))];

清除WK缓存

- (void)cleanCacheAndCookie
{
    //清除cookies
    NSHTTPCookie *cookie;
    NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (cookie in [storage cookies])
    {
        [storage deleteCookie:cookie];
    }
    
    [[NSURLCache sharedURLCache] removeAllCachedResponses];
    NSURLCache * cache = [NSURLCache sharedURLCache];
    [cache removeAllCachedResponses];
    [cache setDiskCapacity:0];
    [cache setMemoryCapacity:0];
    
    WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
    [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                     completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records)
     {
         for (WKWebsiteDataRecord *record  in records)
         {
             
             [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
                                                       forDataRecords:@[record]
                                                    completionHandler:^
              {
                  NSLog(@"Cookies for %@ deleted successfully",record.displayName);
              }];
         }
     }];
}
- (void)dealloc
{
    [_webView stopLoading];
    [_webView setNavigationDelegate:nil];
    [self clearCache];
    [self cleanCacheAndCookie];
}

点击链接无反应


#pragma mark WKNavigationDelegate

-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
    
}

WKWebView计算内容高度

添加KVO

[_webView.scrollView addObserver:selfforKeyPath:@"contentSize"options:NSKeyValueObservingOptionNewcontext:nil];


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if ([keyPath isEqualToString:@"contentSize"]) {
        dispatch_async(dispatch_get_global_queue(0,0), ^{

            //document.documentElement.scrollHeight
            //document.body.offsetHeight
            [_webView evaluateJavaScript:@"document.documentElement.offsetHeight"completionHandler:^(id_Nullable result, NSError * _Nullable error) {

                CGRect frame =_webView.frame;
                frame.size.height = [result doubleValue] + 50;
                _webView.frame = frame;
                _scrollViewHeight =220 + _webView.height;
                _scrollView.contentSize =CGSizeMake(kScreenWidth,_scrollViewHeight);
            }];
        });
    }
}

H5调用了拨打电话功能

先拦截特点scheme,然后执行拨打电话的代码

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // 拦截
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"tel"]) {
        NSString *resourceSpecifier = [URL resourceSpecifier];
        NSString *callPhone = [NSString stringWithFormat:@"telprompt://%@", resourceSpecifier];
        
        decisionHandler(WKNavigationActionPolicyCancel);
        // 拨打
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callPhone]];
        return ;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

参考资料

https://www.jianshu.com/p/20cfd4f8c4ff
https://www.jianshu.com/p/5cf0d241ae12
https://www.jianshu.com/p/6ba2507445e4 *
https://www.jianshu.com/p/4d12d593ba60

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

推荐阅读更多精彩内容