• WKWebView简单使用及关于缓存的问题


    Xcode8发布以后,编译器开始不支持IOS7,所以很多应用在适配IOS10之后都不在适配IOS7了,其中包括了很多大公司,网易新闻,滴滴出行等。因此,我们公司的应用也打算淘汰IOS7。支持到IOS8,第一个要改的自然是用WKWebView替换原来的UIWebView。WKWebView有很多明显优势:

    • 更多的支持HTML5的特性

    • 官方宣称的高达60fps的滚动刷新率以及内置手势

    • 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议,以前很多不方便实现的功能得以实现。文档

    • Safari相同的JavaScript引擎

    • 占用更少的内存   

    WKWebView有两个delegate,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要处理一些跳转、加载处理操作,WKUIDelegate主要处理JS脚本,确认框,警告框等。因此WKNavigationDelegate更加常用。

     初始化

    #pragma mark - Getting With Setting
    - (WKWebView *)webView {
        if (!_webView) {
            _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
            _webView.backgroundColor = [UIColor whiteColor];
            [_webView setUserInteractionEnabled:YES];
            // 导航代理
            _webView.navigationDelegate = self;
            // 与webview UI交互代理
            _webView.UIDelegate = self;
            //加载网页
            NSURLRequest * urlReuqest = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:_urlStr]];
            [_webView loadRequest:urlReuqest];
            //监听属性
            [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:NULL];
            [_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
        }
        return _webView;
    }
    WKNavigationDelegate

    如果需要处理web导航操作,比如链接跳转、接收响应、在导航开始、成功、失败等时要做些处理,就可以通过实现相关的代理方法:

    #pragma mark - WKNavigationDelegate
      // 请求开始前,会先调用此代理方法
      // 与UIWebView的
      // - (BOOL)webView:(UIWebView *)webView 
      // shouldStartLoadWithRequest:(NSURLRequest *)request 
      // navigationType:(UIWebViewNavigationType)navigationType;
      // 类型,在请求先判断能不能跳转(请求)
      - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
        NSString *hostname = navigationAction.request.URL.host.lowercaseString;
        if (navigationAction.navigationType == WKNavigationTypeLinkActivated
      && ![hostname containsString:@".baidu.com"]) {
      // 对于跨域,需要手动跳转
          [[UIApplication sharedApplication] openURL:navigationAction.request.URL];
    
          // 不允许web内跳转
          decisionHandler(WKNavigationActionPolicyCancel);
        } else {
          self.progressView.alpha = 1.0;
          decisionHandler(WKNavigationActionPolicyAllow);
        }
    
          NSLog(@"%s", __FUNCTION__);
      }
    
      // 在响应完成时,会回调此方法
      // 如果设置为不允许响应,web内容就不会传过来
      - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
        decisionHandler(WKNavigationResponsePolicyAllow);
        NSLog(@"%s", __FUNCTION__);
      }
    
      // 开始导航跳转时会回调
      - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
          NSLog(@"%s", __FUNCTION__);
      }
    
      // 接收到重定向时会回调
      - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
          NSLog(@"%s", __FUNCTION__);
      }
    
      // 导航失败时会回调
      - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
          NSLog(@"%s", __FUNCTION__);
      }
    
      // 页面内容到达main frame时回调
      - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation {
          NSLog(@"%s", __FUNCTION__);
      }
    
      // 导航完成时,会回调(也就是页面载入完成了)
      - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
          NSLog(@"%s", __FUNCTION__);
      }
    
      // 导航失败时会回调
      - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
    
      }
    
      // 对于HTTPS的都会触发此代理,如果不要求验证,传默认就行
      // 如果需要证书验证,与使用AFN进行HTTPS证书验证是一样的
      - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler {
          NSLog(@"%s", __FUNCTION__);
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
      }
    
      // 9.0才能使用,web内容处理中断时会触发
      - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
          NSLog(@"%s", __FUNCTION__);
      }

    JavaScript交互处理

    创建配置类及其偏好设置
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    // 设置偏好设置
    config.preferences = [[WKPreferences alloc] init];
    // 默认为0
    config.preferences.minimumFontSize = 10;
    // 默认认为YES
    config.preferences.javaScriptEnabled = YES;
    // 在iOS上默认为NO,表示不能自动通过窗口打开
    config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
    // web内容处理池
    config.processPool = [[WKProcessPool alloc] init];
    // 通过JS与webview内容交互
    config.userContentController = [[WKUserContentController alloc] init];
    // 注入JS对象名称AppModel,当JS通过AppModel来调用时,
    // 我们可以在WKScriptMessageHandler代理中接收到
    [config.userContentController addScriptMessageHandler:self name:@"AppModel"];
    
    //加载web
    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds
                                configuration:config];
    [self.view addSubview:self.webView];
    WKUIDelegate

    与JS原生的alert、confirm、prompt交互,将弹出来的实际上是我们原生的窗口,而不是JS的。在得到数据后,由原生传回到JS:

    #pragma mark - WKUIDelegate
      - (void)webViewDidClose:(WKWebView *)webView {
           NSLog(@"%s", __FUNCTION__);
      }
    
      // 在JS端调用alert函数时,会触发此代理方法。
      // JS端调用alert时所传的数据可以通过message拿到
      // 在原生得到结果后,需要回调JS,是通过completionHandler回调
      - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
        NSLog(@"%s", __FUNCTION__);
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:@"JS调用alert"                 preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
          completionHandler();
        }]];
    
        [self presentViewController:alert animated:YES completion:NULL];
        NSLog(@"%@", message);
      }
    
      // JS端调用confirm函数时,会触发此方法
      // 通过message可以拿到JS端所传的数据
      // 在iOS端显示原生alert得到YES/NO后
      // 通过completionHandler回调给JS端
      - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
        NSLog(@"%s", __FUNCTION__);
    
        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);
      }
    
      // JS端调用prompt函数时,会触发此方法
      // 要求输入一段文本
      // 在原生输入得到文本内容后,通过completionHandler回调给JS
      - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler {
        NSLog(@"%s", __FUNCTION__);
    
        NSLog(@"%@", prompt);
        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];
      }
    注入js

    实现KVO处理方法,在loading完成时,可以注入一些JS到web中。这里只是简单地执行一段web中的JS函数:

    #pragma mark - KVO
      - (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context {
        if ([keyPath isEqualToString:@"loading"]) {
          NSLog(@"loading");
        } else if ([keyPath isEqualToString:@"title"]) {
          self.title = self.webView.title;
        } else if ([keyPath isEqualToString:@"estimatedProgress"]) {
          NSLog(@"progress: %f", self.webView.estimatedProgress);
          self.progressView.progress = self.webView.estimatedProgress;
        }
    
          // 加载完成
        if (!self.webView.loading) {
          // 手动调用JS代码
          // 每次页面完成都弹出来,大家可以在测试时再打开
          NSString *js = @"callJsAlert()";
            [self.webView evaluateJavaScript:js completionHandler:^(id _Nullable response, NSError * _Nullable error) {
            NSLog(@"response: %@ error: %@", response, error);
            NSLog(@"call js alert by native");
          }];
    
          [UIView animateWithDuration:0.5 animations:^{
            self.progressView.alpha = 0;
          }];
        }
      }
    js数据传递
    
    // AppModel是我们所注入的对象
    window.webkit.messageHandlers.AppModel.postMessage({body: response});

    WKWebView使用中遇到的问题

    1.关于缓存的问题

    因为使用了WKWebView,后端的策划人员换图,iOS端没有更新,然后google了好久,最终算是解决了这个问题。
    首先,加载第一个页面。

    _urlStr = @"https://www.baidu.com";
    //设置缓存的请求策略和超时时间
    NSURLRequest * urlReuqest = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:_urlStr] cachePolicy:1 timeoutInterval:30.0f];
    [_webView loadRequest:urlReuqest];

    这时能正常的显示第一个页面,及时更换了图片也能正常的显示。
    但是在跳转另一个URL时,不能设置缓存方式。。。这样就造成了,如果你更换了图片,并且之前你进入了这个页面,就导致了你看到的是以前的页面。我这里找到的处理的方式是在这个WKWebView调用dealloc方法时,把html页面的缓存全部删掉。以下是方法
    //在ViewController销毁时移除KVO观察者,同时清除所有的html缓存
    - (void)dealloc {
        [self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
        [self.webView removeObserver:self forKeyPath:@"title"];
        [self clearCache];
    }
    
    /** 清理缓存的方法,这个方法会清除缓存类型为HTML类型的文件*/
    - (void)clearCache {
        /* 取得Library文件夹的位置*/
        NSString *libraryDir = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES)[0];
        /* 取得bundle id,用作文件拼接用*/
        NSString *bundleId  =  [[[NSBundle mainBundle] infoDictionary]objectForKey:@"CFBundleIdentifier"];
        /*
         * 拼接缓存地址,具体目录为App/Library/Caches/你的APPBundleID/fsCachedData
         */
        NSString *webKitFolderInCachesfs = [NSString stringWithFormat:@"%@/Caches/%@/fsCachedData",libraryDir,bundleId];
    
        NSError *error;
        /* 取得目录下所有的文件,取得文件数组*/
        NSFileManager *fileManager = [NSFileManager defaultManager];
    //    NSArray *fileList = [[NSArray alloc] init];
        //fileList便是包含有该文件夹下所有文件的文件名及文件夹名的数组
        NSArray *fileList = [fileManager contentsOfDirectoryAtPath:webKitFolderInCachesfs error:&error];
        /* 遍历文件组成的数组*/
        for(NSString * fileName in fileList){
            /* 定位每个文件的位置*/
            NSString * path = [[NSBundle bundleWithPath:webKitFolderInCachesfs] pathForResource:fileName ofType:@""];
            /* 将文件转换为NSData类型的数据*/
            NSData * fileData = [NSData dataWithContentsOfFile:path];
            /* 如果FileData的长度大于2,说明FileData不为空*/
            if(fileData.length >2){
                /* 创建两个用于显示文件类型的变量*/
                int char1 =0;
                int char2 =0;
    
                [fileData getBytes:&char1 range:NSMakeRange(0,1)];
                [fileData getBytes:&char2 range:NSMakeRange(1,1)];
                /* 拼接两个变量*/
                NSString *numStr = [NSString stringWithFormat:@"%i%i",char1,char2];
                /* 如果该文件前四个字符是6033,说明是Html文件,删除掉本地的缓存*/
                if([numStr isEqualToString:@"6033"]){
                    [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@/%@",webKitFolderInCachesfs,fileName]error:&error];
                    continue;
                }
            }
        }
    }
  • 相关阅读:
    UVA 1386 Cellular Automaton
    ZOJ 3331 Process the Tasks
    CodeForces 650B Image Preview
    CodeForces 650A Watchmen
    CodeForces 651B Beautiful Paintings
    CodeForces 651A Joysticks
    HUST 1601 Shepherd
    HUST 1602 Substring
    HUST 1600 Lucky Numbers
    POJ 3991 Seinfeld
  • 原文地址:https://www.cnblogs.com/allencelee/p/6709599.html
Copyright © 2020-2023  润新知