美文网首页
初识WKWebView

初识WKWebView

作者: 奶茶007 | 来源:发表于2016-10-27 17:54 被阅读1251次

    公司开始让做一个新iOS项目,由于苹果的更新需要每次发版本审核,没法像服务器一样实时更新,技术部就讨论出原生+HTML混合编程的策略。一想到混合编程就毫不犹豫的开始用UIWebView,但是发现点击按钮十分卡顿,用户体验不是很好。想起苹果在2014年新出的WKWebView,想试下效果如何,结果让我很满意,心里甚是欢喜。但是.......

    调用HTML必然牵扯到交互,以前在网上找的很好用的第三方框架(https://github.com/dukeland/EasyJSWebView)不适用WKWebView,刚开始还自以为是的想着把原来的UIWebView的代理方法替换成对应的WKWebView的代理方法就行了,就埋头在那里替换,替换完之后发现不行,就开始查看WKWebView的官方文档,还是我的功力太浅,看的一知半解,也不知道应该怎么去做,就只好去网上查找优秀的文章。(我想说的是,我解决问题的思路是有问题的,有点想当然的去做,或许应该先去了解WKWebView,对比着WKWebView和UIWebView的优劣,然后决定用哪个,而不是先做发现问题再去想办法解决,幸亏现在是把那些坑填满了。。。如果一开始的选择是个无底洞,不知道又要做多少无用功了)。以下内容就按照我遇到问题的顺序,对WKWebView做一总结:

    一  、WKWebView简介

    WKWebView是现代 WebKit API 在 iOS 8 和 OS X Yosemite 应用中的核心部分。它代替了 UIKit 中的UIWebView和 AppKit 中的WebView,提供了统一的跨双平台 API(iOS和OS)。

    二、WKWebView新特性

    在性能、稳定性、功能方面有很大提升(最直观的体现就是加载网页是占用的内存,模拟器加载百度与开源中国网站时,WKWebView占用23M,而UIWebView占用85M);

    和 Safari 相同的 JavaScript 引擎,允许JavaScript的Nitro库加载并使用(UIWebView中限制);

    支持了更多的HTML5特性;

    自诩拥有 60fps 滚动刷新率、内置手势、高效的 app 和 web 信息交换通道

    将UIWebViewDelegate与UIWebView重构成了14类与3个协议(具体查看苹果官方文档);

    三、WKWebView不足


    WKWebView会忽视默认的网络存储, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。WKWebView有自己的进程,同样也有自己的存储空间用来存储cookie和cache, 其他的网络类如NSURLConnection是无法访问到的。同时WKWebView发起的资源请求也是不经过NSURLProtocol的,导致无法自定义请求。同一个应用,不同WKWebView之间的cookie同步,可以通过使用同一个WKProcessPool实现cookie的同步。(我没有具体实现,如果想尝试,可以参考http://mcfeng.me/blog/2014/11/03/wkwebview-uiwebview-cookie-sync/)

    简单来说就是页面之间cookie同步和共享,以前同一个应用,不同UIWebView之间的Cookie是自动同步的。它们都是保存在NSHTTPCookieStorage中。当UIWebView加载一个URL的时候,在加载完成时候,这个URL response中包含的cookie会自动以NSHttpCookie的形式保存到NSHTTPCookieStorage中。同时,如果在http response中,对cookie进行更新或者删除的话,其结果也会直接反应到NSHTTPCookieStorage存储的cookie数据中。所以,如果需要取出cookie数值,只要调用[NSHTTPCookieStorage sharedHTTPCookieStorage]即可。但是现在需要手动的设置cookie来保持页面之间信息同步。

    四、WKWebView与HTML交互

    WKWebView调用js脚本(以下为示例代码,作为参考)

    ```

    #pragma mark - WKNavigationDelegate

    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{

    NSString *executeString = @"$(document).ready(function(){$('.guanliansearch').removeClass(\"hide\").addClass(\"show\") });";

    [self.webView evaluateJavaScript:executeString completionHandler:^(id _Nullable result, NSError * _Nullable error) {

    }];

    }

    ```

    HTML调用OC方法

    ```
    UIViewController<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler>中WKWebView的初始化,注意要遵守协议

    - (void)viewDidLoad {

    [super viewDidLoad];

    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];

    [configuration.userContentController addScriptMessageHandler:self name:@"goBack"];(goBack为方法名,与html中对应)

    self.webView = [[WKWebView alloc] initWithFrame:viewRect configuration:configuration];

    self.webView.navigationDelegate = self;

    self.webView.UIDelegate = self;

    self.webView.customUserAgent = kCustomUserAgent;

    //让HTML适配屏幕

    self.edgesForExtendedLayout = UIRectEdgeNone;

    self.automaticallyAdjustsScrollViewInsets = NO;

    [self.view addSubview:self.webView];

    //显示加载页

    [self showLoadingView];

    // 1. URL 定位资源,需要资源的地址

    NSURL *url = [NSURL URLWithString:self.webViewUrl];

    // 2. 把URL告诉给服务器,请求,从m.baidu.com请求数据

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    // 设置缓存策略

    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

    // 3. 发送请求给服务器

    [self.webView loadRequest:request];

    }```

    ```

    #pragma mark - WKScriptMessageHandler

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

    NSLog(@"%@",NSStringFromSelector(_cmd));

    NSLog(@"message.body--%@  message.name--%@",message.body,message.name);

    NSArray *messageArray = (NSArray *)message.body;(如果请求为多个参数,强转为数组)

    if ([message.name isEqualToString:@"goBack"]){

    [self  goBack];

    }

    }

    - (void)goBack{

    [self dismissViewControllerAnimated:YES completion:^{

    }];

    }```

    HTML对应的配置

    ```

    <html>

    <head>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <script type="text/javascript">//JS响应方法列表>

    function btnClick1() {

    var useragent = navigator.userAgent;

    if(useragent == “***”){

    window.webkit.messageHandlers.goBack.postMessage(null);(没有参数的话必须写null,如果不写按钮点击也没有反应,如果多个参数写法( ['param1','param2'])  ,即用数组格式;一个参数格式(‘param1’),goBack为方法名)

    }

    }</script>

    <style type="text/css">body {background-color:white;font-size:20px;padding:10;}</style>

    li {padding:5}

    button { width:700; height:100;margin:10}

    </head>

    <body><ul><li><button type="button" onclick="btnClick1()">showToast></button></li></ul></body>

    </html>

    ```

    五,WKWebView  的WKUIDelegate方法的实现

    ```

    #pragma mark - WKUIDelegate(我只是实现了2个作为参考,3个方法分别对应JavaScript 中创建三种消息框:警告框、确认框、提示框,如果不实现相应的方法,则HTML中写的消息框无反应)

    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{

    WBLog(@"webView.URL---%@",webView.URL);

    UIAlertController *alertController1 = [UIAlertController alertControllerWithTitle:message message: nil preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *noAction  = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

    completionHandler();(该方法一定要实现,否则会系统会崩溃,错误日志很奇葩)

    }];

    [alertController1  addAction:noAction];

    [self presentViewController:alertController1  animated:YES completion:^{

    }];

    }

    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message message: nil preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *cancelAction  = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

    completionHandler(WKNavigationResponsePolicyCancel);

    }];

    [alertController  addAction:cancelAction];

    [self presentViewController:alertController  animated:YES completion:^{

    }];

    UIAlertAction *okAction  = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

    completionHandler(WKNavigationResponsePolicyAllow);

    }];

    [alertController  addAction:okAction];

    }

    ```

    五,WKWebView  的cookie共享

    说道cookie肯定会想到session,首先简单了解下:(参考资料:http://blog.csdn.net/fangaoxin/article/details/6952954/)

    会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份Session通过在服务器端记录信息确定用户身份

    Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。

    由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理

    Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

    如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

    以下是我手动设置cookie的代码,(希望有更好的方式,可以多多指教),思路是,当用户登录完成之后,保存页面返回的cookie,主要三个属性,JSESSIONID,uid,token
    用户登录完成之后,每次访问页面都携带有这3个属性的cookie

    ```

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{

    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;

    NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];

    for (NSHTTPCookie *cookie in cookies) {

    //每次都存最新的sessionId

    [SettingBaseTool deleteDatasByKey:[cookie name]];

    [SettingBaseTool saveBaseInfoStringWithKey:[cookie name] value:[cookie value] isDelayUserId:NO];

    }

    //跳转之前传入cookie

    //js函数

    NSString *JSFuncString =

    @"function setCookie(name,value,expires)\

    {\

    var oDate=new Date();\

    oDate.setDate(oDate.getDate()+expires);\

    document.cookie=name+'='+value+';expires='+oDate;\

    }\

    function getCookie(name)\

    {\

    var arr = document.cookie.match(new RegExp('(^| )'+name+'=([^;]*)(;|$)'));\

    if(arr != null) return unescape(arr[2]); return null;\

    }\

    function delCookie(name)\

    {\

    var exp = new Date();\

    exp.setTime(exp.getTime() - 1);\

    var cval=getCookie(name);\

    if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\

    }";

    //拼凑js字符串

    //取出 JSESSIONID  uid token

    NSString *jessionString = @"JSESSIONID";

    NSString *uidString = @"uid";

    NSString *tokenString = @"token";

    NSMutableString *JSCookieString = JSFuncString.mutableCopy;

    NSString *jessionValueString = [SettingBaseTool readBaseInfoStringWithKey:jessionString isDelayUserId:NO];(从我自己封装的方法从沙盒中取出值)

    if ([jessionValueString isNotEmpty]) {

    NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", jessionString, jessionValueString];

    [JSCookieString appendString:excuteJSString];

    }

    NSString *uidValueString = [SettingBaseTool readBaseInfoStringWithKey:uidString isDelayUserId:NO];

    if ([uidValueString isNotEmpty]) {

    NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", uidString, uidValueString];

    [JSCookieString appendString:excuteJSString];

    }

    NSString  *tokenVauleString = [SettingBaseTool readBaseInfoStringWithKey:tokenString isDelayUserId:NO];

    if ([tokenVauleString isNotEmpty]) {

    NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", tokenString, tokenVauleString];

    [JSCookieString appendString:excuteJSString];

    }

    //执行js

    [webView evaluateJavaScript:JSCookieString completionHandler:^(id _Nullable result, NSError * _Nullable error) {

    decisionHandler(WKNavigationResponsePolicyAllow);

    }];

    }

    #pragma mark - WKNavigationDelegate

    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{

    NSString *str = @"document.cookie";(获取页面返回的cookie,然后设置更新)

    [webView evaluateJavaScript:str completionHandler:^(id _Nullable result, NSError * _Nullable error) {

    if ([result isNotEmpty]) {

    NSArray *resultArray  =[result componentsSeparatedByString:@";"];

    if (resultArray.count > 0) {

    for (NSString *resultString in resultArray) {

    NSArray *keyValueArray = [resultString componentsSeparatedByString:@"="];

    if (keyValueArray.count > 1) {

    [SettingBaseTool updateDatasByKey:[keyValueArray[0] stringByReplacingOccurrencesOfString:@" " withString:@""] value:keyValueArray[1] ];(自己封装的方法,更新三个属性)

    }

    }

    }

    }

    }];

    ```

    六,捕获<a href:"9998989">

    //在发送请求之前,决定是否跳转  如果不实现这个代理方法,默认会屏蔽掉打电话等url

    ```

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

    // 允许跳转

    decisionHandler(WKNavigationActionPolicyAllow);

    }

    //页面开始加载

    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{

    NSString *path=[webView.URL absoluteString];

    NSString * newPath = [path lowercaseString];

    //捕获URL,然后用UIApplication打开

    if ([newPath hasPrefix:@"sms:"] || [newPath hasPrefix:@"tel:"] || [newPath hasPrefix:@"apipays:"] || [newPath hasPrefix:@"mqqwpa:"]) {

    UIApplication * app = [UIApplication sharedApplication];

    if ([app canOpenURL:[NSURL URLWithString:newPath]]) {

    [app openURL:[NSURL URLWithString:newPath]];

    }

    return;

    }

    }

    ```

    七,调用第三方,QQ,或者支付宝

    点击QQ和支付宝都没有反应,思考原因,最后发现由于自定义了

    self.webView.customUserAgent  = @“hah-ios”而第三方判断是浏览器还是Android或者iPhone的标准是基于默认值,所以就改成如下

    ```

    [self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {

    NSString *defaultAgent = (NSString *)result;

    self.webView.customUserAgent = [NSString stringWithFormat:@"%@ hah-ios",defaultAgent];

    }];

    ```

    当然代码有些冗余,有待优化。不过有种拨开乌云见明月的感觉。。顿时感觉压力小了很多。。。


    相关文章

      网友评论

          本文标题:初识WKWebView

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