iOS开发WKWebView的使用

作者: Arthur澪 | 来源:发表于2018-08-15 15:33 被阅读0次

    前言

    一直以来项目中都习惯使用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];    //刷新当前页面
    

    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];
    }
    

    参考资料

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

    相关文章

      网友评论

        本文标题:iOS开发WKWebView的使用

        本文链接:https://www.haomeiwen.com/subject/qsoavftx.html