WKWebView 使用及注意事项

WKWebView 是苹果提供的用于在App中进行网页浏览的控件,不过只能在 iOS8 后使用,如果还需要适配 iOS7,那我只能摆一张无奈脸了ㄟ( ▔, ▔ )ㄏ

为什么使用要用 WKWebView

它相对于 UIWebView 有以下几个优点:

  1. 性能和稳定性的大幅提高
  2. 内存占用的减少
  3. 支持更多HTML5特性
  4. 60fps的刷新率以及内置手势的支持
  5. 增加了新的代理方法,可控性更高

更多内容可以查看 WebKit苹果官方链接

如何使用

引入 WebKit

要想使用WKWebView,一定要先引入:

#import <WebKit/WebKit.h>

添加 WKWebView

// WKUserContentController 对象为 JavaScript 提供了一种方式,可以将消息发送到 web 视图,并将用户脚本注入到 web 视图中。
WKUserContentController *userContentController = [[WKUserContentController alloc] init];

// 执行 js,添加 cookies
NSString *js = @"document.cookie='userId=zhangpeng'";
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript];

// 用于初始化 web 视图的配置
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = userContentController;
_config = config;

// 注入 JS 对象名称,当 JS 通过对象名称来调用时,我们可以在 WKScriptMessageHandler 代理中接收到
for (NSString *scriptMessage in self.scriptMessages) {
    [config.userContentController addScriptMessageHandler:self name:scriptMessage];
}

WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, kScreenW, kMainAreaHeightNoTab) configuration:config];
/*
    UIDelegate: web view 的用户界面代理。
    WKUIDelegate类提供了代表网页呈现本地用户界面元素的方法。
*/
webView.UIDelegate = self;
/*
    navigationDelegate: web view 的导航代理。
    WKNavigationDelegate协议的方法帮助您实现在web view接受、加载和完成导航请求过程中触发的自定义行为。
*/
webView.navigationDelegate = self;
[self.view addSubview:webView];
_webView = webView;

代理方法

WKNavigationDelegate

WKNavigationDelegate 中基本都是生命周期相关的代理,下面会按照执行顺序进行介绍

// 1.是否允许跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    NSLog(@"%s", __func__);
    decisionHandler(WKNavigationActionPolicyAllow);
}

// 2.开始加载网页
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
    NSLog(@"%s", __func__);
}

// 3.知道返回内容之后,是否允许加载
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSLog(@"%s", __func__);
    decisionHandler(WKNavigationResponsePolicyAllow);
}

/// 4.当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
    NSLog(@"%s", __func__);
}

// 5.页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    NSLog(@"%s", __func__);
}

// 当跳转失败时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"%s", __func__);
}

// 当web view加载内容失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation {
    NSLog(@"%s", __func__);
}

下面还有几个不常用但是要知道的代理

//当由服务端进行重定向时触发
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"%s", __func__);
}

//进行证书验证时触发
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
    NSLog(@"%s", __func__);
    NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust];
    completionHandler(NSURLSessionAuthChallengeUseCredential, card);
}

//当因为某些问题,导致webView进程终止时触发
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
    NSLog(@"%s", __func__);
}

WKUIDelegate

WKUIDelegate 中我们最常用的就是这个 - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler 代理了。我们可以在这个代理中,将前端中的 alert() 替换为我们自己的弹窗。

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
    [alert addAction:cancelAction];
    [self presentViewController:alert animated:YES completion:nil];
    completionHandler();
}

这个 message 就是前端通过alert()方法传递的内容。一定要写上 completionHandler();,否则在某些情况下会造成Crash,详见 WKWebView那些坑

下面两个方法,和上面的蕾丝,如有需要,可以看情况使用。

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

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;

与js交互

客户端与 JS 交互,有两种方案:

  1. 通过 webkit.messageHandlers 进行交互;
  2. 通过拦截跳转地址进行交互;

下面我们来分别讲讲如何实现:

通过 webkit.messageHandlers 进行交互

这种方案需要我们客户端提前在端上做些准备。我们需要在 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message 代理中写好路由方法,简单来讲就是什么消息体该做什么事。当前端同学通过特定方法调用功能时,我们可以在此代理中接收到消息体,然后我们根据不同的消息内容,进行不同的操作即可。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    // 打印所传过来的参数,只支持NSNumber, NSString, NSDate, NSArray, NSDictionary, NSNull类型
    if ([message.name isEqualToString:@"test1"]) {
        NSLog(@"触发了test1");
    } else if ([message.name isEqualToString:@"test2"]) {
        NSLog(@"触发了test2");
    } else {

    }
}

通过查看 WKScriptMessage,我们可以看到 namebody 两个属性,name 就是注入的 js 对象名称,body 就是前端传给我们的参数。我们根据不同的 name 进行判断,执行不同的操作。
举个例子:
当前端同学调用 test1 时就会打印 触发了test1,而调用 test2 时就会打印 触发了test2

而前端同学就简单了,当需要调用客户端中的方法时,通过下面的方式进行调用及传参:
window.webkit.messageHandlers.对象.postMessage(参数);

对象:就是我们在初始化WKWebView时,通过addScriptMessageHandler 注入的 js 对象名称;
参数:建议用json进行参数的传递,两边约定好的规范,可以提高开发的效率

举个例子:

window.webkit.messageHandlers.test1.postMessage({msg: "test1"});

通过拦截跳转地址进行交互

拦截跳转地址的玩法,和 UIWebView 的玩法一致,在代理中进行拦截,如果有特定的表示,则停止跳转,然后解析 url,做不同的事情。

常见问题

添加Cookies

  • JS 注入的 Cookie,比如 PHP 代码在 Cookie 容器中取是取不到的,javascript 中的 document.cookie 能读取到,浏览器中也能看到。
  • NSMutableURLRequest 注入的 PHP 等动态语言直接能从 $_COOKIE 对象中获取到,但是 js 读取不到,浏览器也看不到。

所以我们的解决办法是:

  1. 在初始化时,通过 js 注入添加 cookies

    // WKUserContentController对象为JavaScript提供了一种方式,可以将消息发送到web视图,并将用户脚本注入到web视图中。
    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    
    // 执行js,添加cookies
    NSString *js = @"document.cookie='userId=zhangpeng'";
    WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    [userContentController addUserScript:cookieScript];
    
  2. 给发出的 request 也添加上 cookies

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0f];
    [request setValue:@"userId=zhangpeng" forHTTPHeaderField:@"Cookie"];
    [_webView loadRequest:request];
    

WKWebView内存泄漏

问题描述

通过持有 WKWebViewdealloc 方法打断点,可以看到控制器并没有走到该方法,猜想是由于 WKUserContentController 对象的 addScriptMessageHandler 方法强引用了控制器本身,而控制器又强引用了 webView,然后 webView 又强引用了 configurationconfiguration 又强引用了 WKUserContentController 对象,最终造成了不能释放。
通过搜索看到了stackoverflow中的一个解决方案,最终解决了问题。

解决方案

1.创建一个新类WeakScriptMessageDelegate

#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>

@interface WeakScriptMessageDelegate : NSObject
@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
@end
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
    self = [super init];
    if (self) {
        _scriptDelegate = scriptDelegate;
    }
    return self;
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}
@end

2.在我们使用 WKWebView 的控制器中引入我们创建的那个类,将注入 js 对象的代码改为:

[config.userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:scriptMessage];

3.在 delloc 方法中通过下面的方式移除注入的 js 对象

[self.config.userContentController removeScriptMessageHandlerForName:scriptMessage];

上面三步就可以解决控制器不能被释放的问题了。O(∩_∩)O~~

1.WKWebView那些坑

本文的所有代码均以上传至 GitHub,如需自取


Title: WKWebView 使用及注意事项

Date: 2017.08.29

Author: zhangpeng

Github: https://github.com/fullstack-zhangpeng

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容