×

# IOS进阶之WKWebView

o翻滚的牛宝宝o 关注
2016.10.08 14:41* 字数 847 阅读 27535 评论 43 喜欢 161 赞赏 1
标题图片

## 前言

Xcode8发布以后，编译器开始不支持IOS7，所以很多应用在适配IOS10之后都不在适配IOS7了，其中包括了很多大公司，网易新闻，滴滴出行等。因此，我们公司的应用也打算淘汰IOS7。

支持到IOS8，第一个要改的自然是用`WKWebView`替换原来的`UIWebView`。WKWebView有很多明显优势:

- 更多的支持HTML5的特性

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

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

- Safari相同的JavaScript引擎

- 占用更少的内存

UIWebView

UIWebView

WKWebView

WKWebView

因此，使用`WkWebview`替换`UIWebView`还是很有必要的。

## 基本使用方法

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

比较常用的方法：

    #pragma mark - lifeCircle
    - (void)viewDidLoad {
        [super viewDidLoad];
        webView = [[WKWebView alloc]init];
        [self.view addSubview:webView];
        [webView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.view);
            make.right.equalTo(self.view);
            make.top.equalTo(self.view);
            make.bottom.equalTo(self.view);
        }];
        webView.UIDelegate = self;
        webView.navigationDelegate = self;
        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];

    }

    #pragma mark - WKNavigationDelegate
    // 页面开始加载时调用
    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{

    }
    // 当内容开始返回时调用
    - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{

    }
    // 页面加载完成之后调用
    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{

    }
    // 页面加载失败时调用
    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{

    }
    // 接收到服务器跳转请求之后调用
    - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{

    }
    // 在收到响应后，决定是否跳转
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{

        NSLog(@"%@",navigationResponse.response.URL.absoluteString);
        //允许跳转
        decisionHandler(WKNavigationResponsePolicyAllow);
        //不允许跳转
        //decisionHandler(WKNavigationResponsePolicyCancel);
    }
    // 在发送请求之前，决定是否跳转
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

         NSLog(@"%@",navigationAction.request.URL.absoluteString);
        //允许跳转
        decisionHandler(WKNavigationActionPolicyAllow);
        //不允许跳转
        //decisionHandler(WKNavigationActionPolicyCancel);
    }
    #pragma mark - WKUIDelegate
    // 创建一个新的WebView
    - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
        return [[WKWebView alloc]init];
    }
    // 输入框
    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
        completionHandler(@"http");
    }
    // 确认框
    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
        completionHandler(YES);
    }
    // 警告框
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
        NSLog(@"%@",message);
        completionHandler();
    }

## OC与JS交互

WKWebview提供了API实现js交互 不需要借助JavaScriptCore或者webJavaScriptBridge。使用WKUserContentController实现js native交互。简单的说就是先注册约定好的方法，然后再调用。

#### JS调用OC方法

oc代码(有误，内存不释放):

    @interface ViewController ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>{
        WKWebView * webView;
        WKUserContentController* userContentController;
    }
    @end
    @implementation ViewController
    #pragma mark - lifeCircle
    - (void)viewDidLoad {
        [super viewDidLoad];
        //配置环境
        WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];
        userContentController =[[WKUserContentController alloc]init];
        configuration.userContentController = userContentController;
        webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];
        //注册方法
        [userContentController addScriptMessageHandler:self  name:@"sayhello"];//注册一个name为sayhello的js方法

        [self.view addSubview:webView];
        [webView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.view);
            make.right.equalTo(self.view);
            make.top.equalTo(self.view);
            make.bottom.equalTo(self.view);
        }];
        webView.UIDelegate = self;
        webView.navigationDelegate = self;
        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];
    }
    - (void)dealloc{
        //这里需要注意，前面增加过的方法一定要remove掉。
        [userContentController removeScriptMessageHandlerForName:@"sayhello"];
    }
    #pragma mark - WKScriptMessageHandler
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
        NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
    }
    @end

上面的OC代码如果认证测试一下就会发现dealloc并不会执行，这样肯定是不行的，会造成内存泄漏。原因是`[userContentController addScriptMessageHandler:self name:@"sayhello"];`这句代码造成无法释放内存。（ps:试了下用weak指针还是不能释放，不知道是什么原因。）因此还需要进一步改进，正确的写法是用一个新的controller来处理,新的controller再绕用delegate绕回来。

oc代码(正确写法):

    @interface ViewController ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>{
        WKWebView * webView;
        WKUserContentController* userContentController;
    }
    @end
    @implementation ViewController
    #pragma mark - lifeCircle
    - (void)viewDidLoad {
        [super viewDidLoad];
        //配置环境
        WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];
        userContentController =[[WKUserContentController alloc]init];
        configuration.userContentController = userContentController;
        webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];
        //注册方法
        WKDelegateController * delegateController = [[WKDelegateController alloc]init];
        delegateController.delegate = self;

        [userContentController addScriptMessageHandler:delegateController  name:@"sayhello"];

        [self.view addSubview:webView];
        [webView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.view);
            make.right.equalTo(self.view);
            make.top.equalTo(self.view);
            make.bottom.equalTo(self.view);
        }];
        webView.UIDelegate = self;
        webView.navigationDelegate = self;
        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];
    }
    - (void)dealloc{
        //这里需要注意，前面增加过的方法一定要remove掉。
        [userContentController removeScriptMessageHandlerForName:@"sayhello"];
    }
    #pragma mark - WKScriptMessageHandler
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
        NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
    }
    @end

WKDelegateController代码：

    #import <UIKit/UIKit.h>
    #import <WebKit/WebKit.h>
    @protocol WKDelegate <NSObject>

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

    @end

    @interface WKDelegateController : UIViewController <WKScriptMessageHandler>

    @property (weak , nonatomic) id<WKDelegate> delegate;

    @end

.m代码：

    #import "WKDelegateController.h"

    @interface WKDelegateController ()

    @end

    @implementation WKDelegateController

    - (void)viewDidLoad {
        [super viewDidLoad];
    }

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
        if ([self.delegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) {
            [self.delegate userContentController:userContentController didReceiveScriptMessage:message];
        }
    }

    @end

h5代码:

    <html>
    <head>
        <script>
    function say()
    {
    //前端需要用 window.webkit.messageHandlers.注册的方法名.postMessage({body:传输的数据} 来给native发送消息
        window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'});
    }
    </script>
    </head>
        <body>
            <h1>hello world</h1>
            <button onclick="say()">say hello</button>
        </body>

    </html>

打印出的log：

     name:sayhello
     body:{
        body = "hello world!";
    }
     frameInfo:<WKFrameInfo: 0x7f872060ce20; isMainFrame = YES; request =   <NSMutableURLRequest: 0x618000010a30> { URL: http://www.test.com/ }>

#### 注意点

- `addScriptMessageHandler`要和`removeScriptMessageHandlerForName`配套出现，否则会造成内存泄漏。
- h5只能传一个参数，如果需要多个参数就需要用字典或者json组装。

#### oc调用JS方法

代码如下：

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

        //say()是JS方法名，completionHandler是异步回调block
        [webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
            NSLog(@"%@",result);
        }];

    }

h5代码同上。

## WebViewJavascriptBridge

一般来说，一个好的UI总有一个大神会开发出一个好的第三方封装框架。WebViewJavascriptBridge的作者也做了一套支持WKWebView与JS交互的第三方框架:WKWebViewJavascriptBridge。

- cocoaPods: pod 'WebViewJavascriptBridge', '~> 5.0.5'

- github地址：https://github.com/marcuswestin/WebViewJavascriptBridge

主要方法如下：

    //初始化方法
    + (instancetype)bridgeForWebView:(WKWebView*)webView;
    + (void)enableLogging;

    //注册函数名
    - (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;

    //调用函数名
    - (void)callHandler:(NSString*)handlerName;
    - (void)callHandler:(NSString*)handlerName data:(id)data;
    - (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

    //重置
    - (void)reset;

    //设置WKNavigationDelegate
    - (void)setWebViewDelegate:(id<WKNavigationDelegate>)webViewDelegate;

基本的实现方法和上面写的差不多，就是封装了一下，有兴趣的童鞋可以自己pod下来使用。

#### 我是翻滚的牛宝宝，欢迎大家评论交流~

## 补充

前不久把项目中的UIWebView更新到WkWebView，解决了一大堆问题，但是也遗留了一大堆问题，比方说cookie。

以前UIWebView会自动去NSHTTPCookieStorage中读取cookie，但是WKWebView并不会去读取,因此导致cookie丢失以及一系列问题，解决方式就是在request中手动帮其添加上。

    mainWebView.UIDelegate = self;
    mainWebView.navigationDelegate = self;
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]];
    [request addValue:[self readCurrentCookieWithDomain:@"http://www.test.com/"] forHTTPHeaderField:@"Cookie"];
    [mainWebView loadRequest:request];

    - (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{
        NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        NSMutableString * cookieString = [[NSMutableString alloc]init];
        for (NSHTTPCookie*cookie in [cookieJar cookies]) {
            [cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value];
        }

    //删除最后一个“；”
        [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
        return cookieString;
    }

但是这只能解决第一次进入的cookie问题，如果页面内跳转（a标签等）还是取不到cookie，因此还要再加代码。

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

       //取出cookie
        NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        //js函数
        NSString *JSFuncString =
    @"function setCookie(name,value,expires)\
        {\
        var oDate=new Date();\
        oDate.setDate(oDate.getDate()+expires);\
    document.cookie=name+'='+value+';expires='+oDate+';path=/'\
        }\
        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字符串
        NSMutableString *JSCookieString = JSFuncString.mutableCopy;
        for (NSHTTPCookie *cookie in cookieStorage.cookies) {
        NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
            [JSCookieString appendString:excuteJSString];
        }
        //执行js
        [webView evaluateJavaScript:JSCookieString completionHandler:nil];

    }

小礼物走一走，来简书关注我

赞赏支持

ios开发笔记
© 著作权归作者所有
举报文章
关注 o翻滚的牛宝宝o

写了 26980 字，被 401 人关注，获得了 501 个喜欢

最美之物，永不凋零
喜欢
161
更多分享

被以下专题收入，发现更多相似内容
iOS学习记录
iOS程序猿
iOS 开发
将来跳槽用
橙红科技有限公司
牛叉的demo
移动前沿
展开更多
IOS进阶之WKWebView

2016-10-26o翻滚的牛宝宝oCocoaChina ▲点击上方“CocoaChina”关注即可免费学习iOS开发 原文链接：http://www.jianshu.com/p/4fa8c4eb1316 前言 Xcode8发布以后，编译器开始不支持IOS7，所以很多应用在...

飞雯焕日
IOS进阶之WKWebView

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

花无缺_
IOS进阶之WKWebView

基本使用方法WKWebView有两个delegate,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要处理一些跳转、加载处理操作，WKUIDelegate主要处 理JS脚本，确认框，警告框等。因此WKNavi...

远0
（转）WKWebview 处理

前言 关于UIWebView的介绍，相信看过上文的小伙伴们，已经大概清楚了吧，如果有问题，欢迎提问。 本文是本系列文章的第二篇，主要为小伙伴们分享下WKWebView相关的内容： iOS中UIWebView与WKWebView、JavaScript与OC交互、Cookie管...

CoderLF
iOS中UIWebView与WKWebView、JavaScript与OC交互、Cookie管理看我就够（中）

前言 关于UIWebView的介绍，相信看过上文的小伙伴们，已经大概清楚了吧，如果有问题，欢迎提问。 本文是本系列文章的第二篇，主要为小伙伴们分享下WKWebView相关的内容： iOS中UIWebView与WKWebView、JavaScript与OC交互、Cookie管...

Dark_Angel
原则

我的交友原则: 1.我不接受谁连续耍我三次，第一次尚可做朋友，第二次彻底远离，我不希望第三次发生 2.我不喜欢心里很穷困的人，没有钱不是问题，没有几个生下来就有钱的，但是心里很贫穷，不值得深交 3.君子之交淡如水，为了共同的目标交友，一起做事，别牵扯太多 4.我喜欢能够为长...

我不是注销了吗
等

就像雨伞在等雨 蒲公英在等风 寒冰在等阳光 树叶在等秋天 我 在等你

空瓶小宅
第一次听说地震是这么来的

1、A：马上啪啪啪叫马震，车上啪啪啪叫车震，那地上啪啪啪叫什么？ B：叫地震呗！ A：哦，怪不得日本是世界上地震最多的国家！ 2、好友问我：你去过农村没？ 我：去过。 他又问：那你放过牛没？ 我：没。 他：你TM居然连牛都不放过！！！ 我@#$@#$%@#% 3、生产TT的...

晓明同学来了
虐心断肠相思苦

冬已近，夜薄凉 明月清辉，惹来孤旅离伤 一缕寒风侵袭 片片惦念，零碎过往 几许惆怅 曾与君漫步十里长廊 看蝴蝶飞进残阳 湖心亭畔摇椅悠扬节拍 潋滟菡萏初放馨香 心随飘飞的絮，丢失驻场 身如孤寂的夜灯，瘦影昏黄 牵挂的情丝缠绕着离人的心角 思念迷离着悲情，栖落轩窗 将锵铿的诺...

山上人家123
懒惰

懒惰就像装满墨而长久未打开的砚，墨汁充满了砚盖与砚盒之间细小的缝隙，经过时间的锤炼，变干，变的强有力。最后，当你突然某天想把它打开，却怎么也打不 开了。 人生而有惰性，其可怕之处就在于若不加管理，它只会越累越多，累到向前迈一步都成了负担，然而人还沉浸其中，全然不知地“享受”。

阳光正好Q