美文网首页
WKWebView使用总结

WKWebView使用总结

作者: AiOSER | 来源:发表于2018-05-08 16:49 被阅读0次

    WKWebView在工作中的使用广泛,所以想总结一下方便日后使用,直接上代码

    .h文件

    #import <WebKit/WebKit.h>
    #import "CMWeakScriptMessageDelegate.h"
    
    typedef void(^JYWebProgressEventBlock)(NSInteger tag);
    
    @interface CMBaseWebViewController : UIViewController<WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler>
    /** 网页 */
    @property (nonatomic, strong) WKWebView *webView;
    /** 进度条 */
    @property (strong, nonatomic) UIProgressView *progressView;
    /** 返回 */
    @property(nonatomic,assign)BOOL canGoBack;
    /** 前进 */
    @property(nonatomic,assign)BOOL canGoForward;
    
    @property(nonatomic,copy) JYWebProgressEventBlock blockProgress;
    
    @property (nonatomic, strong)NSDictionary *baseUADic;
    
    -(NSString*)DataTOjsonString:(id)object;
    
    /**清缓存*/
    - (void)cleanWebViewCache;
    

    .m文件

    #import "CMBaseWebViewController.h"
    
    @interface CMBaseWebViewController ()
    
    @end
    
    @implementation CMBaseWebViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // 创建webView
        [self setUpWebView];
        // 创建进度条
        [self setUpProgressView];
    }
    
    /**
     * 布局
     */
    - (void)viewDidLayoutSubviews
    {
        [super viewDidLayoutSubviews];
        
    }
    
    -(NSString*)DataTOjsonString:(id)object
    {
        NSString *jsonString = nil;
        NSError *error;
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
                                                           options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
                                                             error:&error];
        if (! jsonData) {
            NSLog(@"Got an error: %@", error);
        } else {
            jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        }
        return jsonString;
    }
    
    /**
     *  创建WebView
     */
    - (void)setUpWebView
    {
        //进行配置控制器
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
        //实例化对象
        configuration.userContentController = [[WKUserContentController alloc] init];
        //重要:调用JS方法
        [configuration.userContentController addScriptMessageHandler:(id<WKScriptMessageHandler>)[[CMWeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"jsCallbackMethod"];
        [configuration.userContentController addScriptMessageHandler:(id<WKScriptMessageHandler>)[[CMWeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"ios_pop"];
        [configuration.userContentController addScriptMessageHandler:(id<WKScriptMessageHandler>)[[CMWeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"updatePageTitle"];
        [configuration.userContentController addScriptMessageHandler:(id<WKScriptMessageHandler>)[[CMWeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"jsCallbackMethodResult"];
        
        
        // 创建webView
        self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
        self.webView.UIDelegate = self;
        self.webView.navigationDelegate = self;
        self.webView.frame = self.view.bounds;
        
    //    NSDictionary *dic = @{
    //                          @"appType":AppType,
    //                          @"appNo":[JYKeyChainManage readValueWithIdentifier:KEY_IN_KEYCHAIN_UUID],
    //                          @"appVersion":AppVersion,
    //                          @"token":[JYUser sharedInstance].token
    //                          };
        
    //
    //
    //    self.webView.customUserAgent = [self DataTOjsonString:dic];
        [self.view addSubview:self.webView];
        // KVO
        [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
        [self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
    }
    
    /**
     *  创建进度条
     */
    - (void)setUpProgressView
    {
        self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
        self.progressView.frame = CGRectMake(0, 0, kDeviceWidth, 0);
        self.progressView.progressTintColor = HRGB(@"43C01C");
        [self.view addSubview:self.progressView];
    }
    
    /**
     *  KVO
     */
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
        if ([keyPath isEqualToString:@"estimatedProgress"]) {
            if (object == _webView) {
                [self.progressView setAlpha:1.0f];
                [self.progressView setProgress:self.webView.estimatedProgress animated:YES];
                if(self.webView.estimatedProgress >= 1.0f) {
                    [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseIn animations:^{
                        [self.progressView setAlpha:0.0f];
                    } completion:^(BOOL finished) {
                        [self.progressView setProgress:0.0f animated:NO];
                    }];
                }
                if (self.blockProgress) {
                    self.blockProgress(0);
                }
            } else {
                [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
            }
        } else if ([keyPath isEqualToString:@"title"]) {
            if (object == self.webView) {
                if (self.webView.title.length) {
                    self.navigationItem.title = self.webView.title;
                }
            } else {
                [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
            }
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    
    #pragma mark - WKScriptMessageHandler
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
    {
        //重要:由子类实现具体内容
    }
    
    #pragma mark - WKNavigationDelegate
    // 对于HTTPS的都会触发此代理,如果不要求验证,传默认就行
    // 如果需要证书验证,与使用AFN进行HTTPS证书验证是一样的
    - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler
    {
        completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
    }
    
    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
    {
        self.canGoBack = webView.canGoBack;
        self.canGoForward = webView.canGoForward;
    }
    
    - (void)popVC
    {
        if ((self.canGoBack == NO && self.canGoForward == NO) || (self.canGoBack == NO && self.canGoForward == YES)) {
            [self.navigationController popViewControllerAnimated:YES];
            return;
        }
        
        if (self.canGoBack == YES && self.canGoForward == NO) {
            [self.webView goBack];
        }
    }
    
    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
    {
        [CMDefaultInfoViewTool dismissFromView:self.webView];
    }
    
    // 全局:处理拨打电话以及Url跳转等等
    - (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];
            /// 防止iOS 10及其之后,拨打电话系统弹出框延迟出现
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callPhone]];
            });
        }
        decisionHandler(WKNavigationActionPolicyAllow);
    }
    
    - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
    {
        [CMDefaultInfoViewTool showLoadFailureView:self.webView];
    }
    
    - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation
    {
        
    }
    
    #pragma mark - WKUIDelegate
    //- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
    //{
    //    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"测试" message:message preferredStyle:UIAlertControllerStyleAlert];
    //    [alert addAction:[UIAlertAction actionWithTitle:@"关闭" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    //        completionHandler();
    //    }]];
    //
    //    [self presentViewController:alert animated:YES completion:nil];
    //}
    
    /**
     *  控制器销毁
     */
    - (void)dealloc
    {
        JYLog(@"%@ ----- dealloc", [self class])
        [self.progressView setAlpha:0.0f];
        [self.progressView removeFromSuperview];
        
        [self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
        [self.webView removeObserver:self forKeyPath:@"title"];
    }
    
    /**
     *  清除缓存
     */
    - (void)cleanWebViewCache
    {
        if ([[UIDevice currentDevice] systemVersion].floatValue >= 9.0) {
            NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
            NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
            [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
            }];
        } else {
            NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
            NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
            NSError *errors;
            [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
        }
    }
    @end
    

    工作中遇到的问题:

    • 1.接第三方sdk的时候,webView显示的title有问题,导致体验不好,比如:抓取到的title显示为: “xxx数据爬虫系统”这个时候为了体验更好我们会采用KVO来判断当前抓取到的title是什么并且做相对应的控制
    - (void)viewDidLoad {
    //监听title,因为会先显示title:“XXX数据爬虫系统”,所以要把此title隐藏
         [self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
    }
    

    对应的实现方法:

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:@"title"]){
            if (object == self.webView) {
                if (![self.webView.title isEqualToString:@"XXX数据爬虫系统"]) {
                    self.title = self.webView.title;
                }
            }else{
                [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
            }
        }
    }
    

    当然我们还要移除KVO:

    -(void)dealloc {
         [self.webView removeObserver:self forKeyPath:@"title"];
    }
    

    潜在的问题有可能出现在dealloc中对KVO的注销上。当对同一个keypath进行两次removeObserver时会导致程序crash,这种情况常常出现在父类有一个KVO,父类在dealloc中remove了一次,子类又remove了一次的情况下。不过我们可以通过代码中context字段来标识出到底KVO是superClass注册的,还是self注册的。我们可以分别在父类以及本类中定义各自的context字符串,比如在本类中定义context为@"SonContext";然后在dealloc中remove observer时指定移除的自身添加的observer。这样iOS就能知道移除的是自己的KVO,而不是父类中的KVO,避免二次remove造成crash。

    • 2.当我们接入第三方SDK时其中有个问题就是在本app内打开淘宝应用,但是iOS加载出来的页面无法显示调到淘宝按钮,android却可以显示,我们通过重写UserAgent解决这个问题。
    self.webView.customUserAgent = [NSString stringWithFormat:@"%@%@",@"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1/",[JYCommonObj convertToJsonData:dicM]];
    
    • 3.还有个问题就是我们要判断本机是否已经安装了淘宝app,同样的问题android可以直接判断,但是iOS却无法直接判断我们要在代理方法里截取相关URL来做本地判断
    -(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
        
        //'stringByReplacingPercentEscapesUsingEncoding:' is deprecated: first deprecated in iOS 9.0 - Use -stringByRemovingPercentEncoding instead, which always uses the recommended UTF-8 encoding.
        //NSString *urlStr = [navigationAction.request.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSString *urlStr = [navigationAction.request.URL.absoluteString stringByRemovingPercentEncoding];
        NSLog(@"decidePolicyForNavigationAction, navigationAction.request.URL = %@", urlStr);
        if ([urlStr containsString:@"taobao://"]) {
            
            // 1.获取应用程序App-B的Page1页面的URL
            NSURL *appBUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@",urlStr]];
            // 2.判断手机中是否安装了对应程序
            if ([[UIApplication sharedApplication] canOpenURL:appBUrl]) {
                // 3. 打开应用程序App-B的Page1页面
                [[UIApplication sharedApplication] openURL:appBUrl];
            } else {
                NSLog(@"没有安装");
            }
            
        }
        
        decisionHandler(WKNavigationActionPolicyAllow);
    }
    
    • 4.配合h5测试弹框显示某些信息,不写此方法就算h5写了弹框iOS客户端也无法显示
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
    {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"测试" message:message preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"关闭" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
        }]];
        
        [self presentViewController:alert animated:YES completion:nil];
    }
    
    • 5.与后台交互的回调方法: 具体交互规则要与h5定义好。
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
        [super userContentController:userContentController didReceiveScriptMessage:message];
        
        WEAKSELF
        NSLog(@"%@",message.body);
        
        NSString *str = (NSString*)message.body;
        NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
        NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
        
        if ([dic[@"type"] isEqualToString:@"gxb_success"]) {
            NSLog(@"成功");
        }else if ([dic[@"type"] isEqualToString:@"gxb_error"]) {
            NSLog(@"失败");
        }
    }
    
    

    重要通知: iOS11以下版本的WKWebView不支持POST请求的发送。
    之前不知道这个问题,直到有一天给后台post参数的是发现有的手机不成功。经过各种资料各种查之后,发现是iOS11以下版本的WKWebView不支持POST请求的发送。
    可以加个判断:@available(iOS 11.0, *)

    相关文章

      网友评论

          本文标题:WKWebView使用总结

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