美文网首页人猿星球iOS 程序员iOS Fun
WKWebview的使用详解和填坑

WKWebview的使用详解和填坑

作者: 乡水情缘 | 来源:发表于2017-04-27 15:43 被阅读2189次

    虽然WKWebView是在Apple的WWDC 2014随iOS 8和OS X 10.10出来的,是为了解决UIWebView加载速度慢、占用内存大的问题。但是由于之前还要适配iOS7,所以就没有使用。现在项目都适配iOS 8以上了,所以就开始使用WKWebView了,但是发现在使用的时候有好多坑,希望这篇文章能带大家绕过坑,更好的使用WKWebView。
    这篇文章主要介绍了以下问题,方便小伙伴们查阅:
    1. WKWebView的基本介绍和使用
    2. WKWebView 加载本地的HTML ,css ,js
    3. WKWebView和JavaScript的交互
    4. WKWebView 默认不弹出js的alert问题
    5.WKWebView 默认是不能识别电话号码
    6.WKWebView 拦截js通过window.open() 打开的窗口
    7.WKWebView解决文字显示太小问题
    8. 解决WKWebView加载POST请求无法发送参数问题

    下面开始说第一个问题** WKWebView**基本使用

    1.创建 跟UIWebview 一样

    // 创建WKWebView
        WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
        // 设置访问的URL
        NSURL *url = [NSURL URLWithString:@"http://www.jianshu.com"];
        // 根据URL创建请求
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        // WKWebView加载请求
        [webView loadRequest:request];
        // 将WKWebView添加到视图
        [self.view addSubview:webView];
    

    UIWebView和WKWebView的代理方法做一个对比
    1.准备加载页面

    UIWebViewDelegate: - webView:shouldStartLoadWithRequest:navigationType
     WKNavigationDelegate: - webView:didStartProvisionalNavigation:
    

    2.内容开始加载

    UIWebViewDelegate: - webViewDidStartLoad:
     WKNavigationDelegate: - webView:didCommitNavigation:
    

    3.页面加载完成

    UIWebViewDelegate: - webViewDidFinishLoad:
     WKNavigationDelegate: - webView:didFinishNavigation:
    

    4.页面加载失败

    UIWebViewDelegate: - webView:didFailLoadWithError:
     WKNavigationDelegate: - webView:didFailNavigation:withError:
     WKNavigationDelegate: - webView:didFailProvisionalNavigation:withError:
    

    可以看到很简单,和UIWebView并没有多少差别,然而性能就刷刷刷的提上去了,是不是很爽呢?如果你只是简单的集成个Web页到App,这些已经够了。不过很多时候并没有那么简单,还需要处理各种东西,那么接着往后看。

    接下来我来说第二个问题 WKWeebView 加载 本地HTML

    UIWebview 加载本地的HTML 的问题,在此就不在多说了,想了解的小伙伴可以参考我之前的一篇文章
    webView中引入本地html,image,js,css文件的方法http://www.jianshu.com/p/afc9e6b68090
    本文主要说明一下 WKWeebView 加载 本地HTML

    当使用loadRequest来读取本地的HTML时,WKWebView是无法读取成功的,后台会出现如下的提示:
    Could not create a sandbox extension for /
    原因是WKWebView是不允许通过loadRequest的方法来加载本地根目录的HTML文件。
    而在iOS9的SDK中加入了以下方法来加载本地的HTML文件:
    [WKWebView loadFileURL:allowingReadAccessToURL:]
    但是在iOS9以下的版本是没提供这个便利的方法的。以下为解决方案的思路,就是在iOS9以下版本时,先将本地HTML文件的数据copy到tmp目录中,然后再使用loadRequest来加载。但是如果在HTML中加入了其他资源文件,例如js,css,image等必须一同copy到temp中。这个是最蛋疼的事情了。

    解决方法如下

    直接copy代码可以使用

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self setNavTitle:self.titleName];
        self.webview = [[WKWebView alloc]initWithFrame:ccr(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT-64)];
        
        NSString *path = [[NSBundle mainBundle] pathForResource:self.loadHtmlName ofType:@"html"];
    //下面是加载css 项目不需要的飘过
          NSString *path2 = [[NSBundle mainBundle] pathForResource:@"calculus" ofType:@"css"];
    //下面是加载图片 项目不需要的飘过
        NSString *path3 = [[NSBundle mainBundle] pathForResource:@"headerbg" ofType:@"jpg"];
        if(path){
            if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
                // iOS9. One year later things are OK.
                NSURL *fileURL = [NSURL fileURLWithPath:path];
                [self.webview loadFileURL:fileURL allowingReadAccessToURL:fileURL];
    //注释的方法和上面的方法是等价的,两者都可以使用
    //            NSString * htmlCont = [NSString stringWithContentsOfFile:path
    //                                                                                       encoding:NSUTF8StringEncoding
    //                                                                                          error:nil];
    //            [self.webview loadHTMLString:htmlCont baseURL:[NSBundle mainBundle].resourceURL];
            } else {
                // iOS8
                
                NSURL *fileURL = [self fileURLForBuggyWKWebView8:[NSURL fileURLWithPath:path]];
                [self fileURLForBuggyWKWebView8:[NSURL fileURLWithPath:path2]];
                [self fileURLForBuggyWKWebView8:[NSURL fileURLWithPath:path3]];
                NSURLRequest *request = [NSURLRequest requestWithURL:fileURL];
                [self.webview loadRequest:request];
            }
        }
        
        [self.view addSubview:self.webview];
    }
    

    //把图片和css copy到tmp目录中

    - (NSURL *)fileURLForBuggyWKWebView8:(NSURL *)fileURL {
        NSError *error = nil;
        if (!fileURL.fileURL || ![fileURL checkResourceIsReachableAndReturnError:&error]) {
            return nil;
        }
        // Create "/temp/www" directory
        NSFileManager *fileManager= [NSFileManager defaultManager];
        NSURL *temDirURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:@"www"];
        [fileManager createDirectoryAtURL:temDirURL withIntermediateDirectories:YES attributes:nil error:&error];
        
        NSURL *dstURL = [temDirURL URLByAppendingPathComponent:fileURL.lastPathComponent];
        // Now copy given file to the temp directory
        [fileManager removeItemAtURL:dstURL error:&error];
        [fileManager copyItemAtURL:fileURL toURL:dstURL error:&error];
        // Files in "/temp/www" load flawlesly :)
        return dstURL;
    }
    

    当时选择WKWebView就是为了提高性能,但是没有想到遇到这么多坑,从看iOS 9才解决了iOS 8无法加载本地样式的问题,有时候苹果解决问题的速度还有略慢的,到现在POST请求参数都发不出去也真是不应该。不过没办法,先解决了,说不定iOS 10 出来之后解决了呢。

    接下来我们开始说第三个问题 WKWebView和JavaScript的交互

    WKWebView和JavaScript的交互主要涉及到两个方面,一个是OC调用JavaScript ,另一个是 JavaScript 调用OC的方法,

    在WebKit框架中,有WKWebView可以替换UIKit的UIWebView和AppKit的WebView,而且提供了在两个平台可以一致使用的接口。WebKit框架使得开发者可以在原生App中使用Nitro来提高网页的性能和表现,Nitro就是Safari的JavaScript引擎,WKWebView不支持JavaScriptCore的方式但提供message handler的方式为JavaScript与Native通信。(这个引自天狐博客,更多的与UIWebView或者WKWebView的交互方法可以在这里看到)。

    1. OC调用JavaScript

    OC调用JavaScrippt是相对来说比较简单的
    只需要在调用的地方添加下面一句代码即可

    //showAlert()是js里面的方法,这样就可以实现调用js方法
    [self.webView evaluateJavaScript:@"showAlert('奏是一个弹框')" completionHandler:^(id item, NSError * _Nullable error) {
            // Block中处理是否通过了或者执行JS错误的代码
        }];
    
    2. JavaScript 调用OC的方法,相对来说复杂一点

    这地方需要两个配置,一个是OC代码的配置,另一个是JS代码的配置,下面先说一下OC代码的配置,细心的小伙伴可能已经发现了,创建WKWebView的时候,除了有- initWithFrame:方法外,还有一个高端的方法:- initWithFrame:configuration:方法。
    #######OC代码的配置
    1.配置 WKWebView

    // 创建配置
          WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
          // 创建UserContentController(提供JavaScript向webView发送消息的方法)
          WKUserContentController* userContent = [[WKUserContentController alloc] init];
          // 添加消息处理,注意:self指代的对象需要遵守WKScriptMessageHandler协议,结束时需要移除
    //NativeMethod 这个方法一会要与JS里面的方法写的一样
          [userContent addScriptMessageHandler:self name:@"NativeMethod"];
          // 将UserConttentController设置到配置文件
          config.userContentController = userContent;
          // 高端的自定义配置创建WKWebView
          WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds configuration:config];
          // 设置访问的URL
          NSURL *url = [NSURL URLWithString:@"http://www.jianshu.com"];
          // 根据URL创建请求
          NSURLRequest *request = [NSURLRequest requestWithURL:url];
          // WKWebView加载请求
          [webView loadRequest:request];
          // 将WKWebView添加到视图
          [self.view addSubview:webView];
    
    2.实现协议方法

    好了,现在万事俱备,只欠东风了。东风是什么呢,就是该在哪儿处理。可以看到WKScriptMessageHandler的协议里面只有一个方法,就是:

     - userContentController:didReceiveScriptMessage:
    

    相信聪明的你已经猜到了。是的,就是在这个代理方法里面操作:如果JavaScript执行已经写好的:window.webkit.messageHandlers.NativeMethod.postMessage("就是一个消息啊");这行代码,这个代理方法就会走,并且会有个WKScriptMessage的对象,这个WKScriptMessage对象有个name属性,拿到之后你会发现,就是我们注册的NativeMethod这个字符串,这时候你就可以手动调用Native的方法了。如果有多个方法需要调用的话怎么办,看到JavaScript中postMessage()方法有一个参数了没有,可以根据这里的参数来区分调用原生App的哪个方法。

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
              // 判断是否是调用原生的
              if ([@"NativeMethod" isEqualToString:message.name]) {
                  // 判断message的内容,然后做相应的操作
                  if ([@"close" isEqualToString:message.body]) {
    
                  }
              }
          }
    
    2.JavaScript的配置

    JavaScript调用Native的方法就需要前端和Native的小伙伴们配合了,需要前端的小伙伴在JS的方法中调用:

    window.webkit.messageHandlers.NativeMethod.postMessage("就是一个消息啊");
    

    这行代码。请注意,这个NativeMethod是和App中要统一的,配置方法将在下面的Native中书写。
    这地方贴一下js代码

    function callOC(func,param){
            window.webkit.messageHandlers. NativeMethod.postMessage('传递的参数');
     }
    

    注意:
    第一:实现以上代码的时候不要忘记实现** WKScriptMessageHandler**协议
    第二:上面将当前ViewController设置为MessageHandler之后需要在当前ViewController销毁前将其移除(dealloc方法),否则会造成内存泄漏。

      [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"NativeMethod"];
    

    第三:来说一下如果有多个方法的时候改如何区分,我这简单说两个区分方法,一个是通过 [userContent addScriptMessageHandler:self name:@"NativeMethod"];来设置多个不同的name ,然后在协议方法里面进行区分,另一个方法是通过 同一个window.webkit.messageHandlers.NativeMethod.postMessage("close");中的name里面的postMessage(来传递参数),然后在协议方法里面区分message.body
    Android 的小伙伴可以参照这篇文章 http://blog.csdn.net/it1039871366/article/details/46372207
    js区分Android和ios的方法

               var u = navigator.userAgent;
                var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端
                var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
              
                    if(isAndroid){
                        window.Android.alipayOrder();
                    }
                    if(isiOS){
                        window.webkit.messageHandlers.alipayOrder.postMessage(r);
                    }
    
    第四个问题 WKWebview 默认是不弹出js的alert 要想可以弹出alert 需要手动的设置代理实现
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
    

    协议方法
    具体的实现方法是,我们采用源生的UIAlertController 来实现弹出框,获取js里面的alert内容显示出来

    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
            NSLog(@"点击了取消按钮==%@",message);
        }])];
        [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
            NSLog(@"点击了确定按钮==%@",message);
        }])];
        
        [self presentViewController:alertController animated:YES completion:nil];
        
    }
    
    第五个问题 WKWebview 默认是不能识别电话号的,这里需要通过实现一个协议来实现拨打电话的功能
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
    

    具体的代码如下:

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
        NSURL *URL = navigationAction.request.URL;
        NSLog(@"获取到URL========%@",URL);
        NSString *scheme = [URL scheme];
        UIApplication *app = [UIApplication sharedApplication];
        // 打电话
        if ([scheme isEqualToString:@"tel"]) {
            if ([app canOpenURL:URL]) {
                [app openURL:URL];
                // 一定要加上这句,否则会打开新页面
                decisionHandler(WKNavigationActionPolicyCancel);
                return;
            }
        }
        decisionHandler(WKNavigationActionPolicyAllow);
    }
    

    注:对应html代码采用的是a标签

    <a href="tel:18158711698">识别电话号码18158711698,进行拨打电话</a>
    
    问题六: WKWebView 默认拦截open.window() 打开新的页面
    - (WKWebView )webView:(WKWebView )webView createWebViewWithConfiguration:(WKWebViewConfiguration )configuration forNavigationAction:(WKNavigationAction )navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
    会拦截到window.open()事件.
    只需要我们在在方法内进行处理
    if (!navigationAction.targetFrame.isMainFrame) {
    [webView loadRequest:navigationAction.request];
    }
    
    问题七:WKWebView解决显示字体太小的问题

    在使用WKWebView的时候,常常会碰到显示内容比实际css设置的样式不能正常显示,内容普遍的偏小。其实导致这样问题的根源是少了HTML5的meta标签。解决的办法可以在iOS端添加以下的内容,当然也可以让后台添加完整的HTML5的格式。如果要在iOS端指定字体的大小也是可以的(不推荐在客户端设置字体大小)。

     NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    
     WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    [wkUController addUserScript:wkUScript];
    
    WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
    wkWebConfig.userContentController = wkUController;
    
    _myWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0,CGRectGetMaxY(headerView.frame)+10, M_S.width,M_S.height - CGRectGetMaxY(headerView.frame) - 40) configuration:wkWebConfig];
    

    客户端设置字体大小eg:

    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
        //修改字体大小 300%
        [ webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '200%'" completionHandler:nil];
    
        //    //修改字体颜色  #9098b8
        //    [ webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextFillColor= '#9098b8'" completionHandler:nil];
    
    }
    

    郑州高端设计
    推荐博客 http://blog.csdn.net/yuanmengong886/article/details/55051036
    https://zhuanlan.zhihu.com/p/24990222

    相关文章

      网友评论

      • 姑娘丶你命里缺我:楼主,问题6虽然解决了window.open的问题,但是有一种情况是,window.open新页面是携带了post参数的,请问这种情况如何正确的跳转呢
      • James_Geng:亲测,WKWebView 下最后一种方法可以用
      • JayAlvin:请问iOS APP在后台的时候,WKWebView能收到js postMessage传过来的消息吗
      • 子_夜:非常感谢, 第七个问题解决了困扰我很久的问题.
      • 9c747f19a8ab:您好,我在使用WKWebView时,碰到了页面内容大于手机屏幕的情况,我通过设置你问题7中的配置,但是没起作用,Webview的页面仍然宽于手机屏幕(可以左右滑动)。
        乡水情缘:@世界欠我一个你 可以让后台给的h5页面设置meta 标签

      本文标题:WKWebview的使用详解和填坑

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