使用WKWebView替换UIWebView

作者: ch32053 | 来源:发表于2015-11-18 17:28 被阅读125627次

开发App的过程中,常常会遇到在App内部加载网页,通常用UIWebView加载。这个自iOS2开始使用的网页加载器一直是开发的心病:加载速度慢,占用内存多,优化困难。如果加载网页多,还可能因为过量占用内存而给系统kill掉。各种优化的方法效果也不那么明显(点击查看常用优化方法)。

iOS8以后,苹果推出了新框架Webkit,提供了替换UIWebView的组件WKWebView。各种UIWebView的问题没有了,速度更快了,占用内存少了,一句话,WKWebView是App内部加载网页的最佳选择!

先看下 WKWebView的特性:

  1. 在性能、稳定性、功能方面有很大提升(最直观的体现就是加载网页是占用的内存,模拟器加载百度与开源中国网站时,WKWebView占用23M,而UIWebView占用85M);
  2. 允许JavaScript的Nitro库加载并使用(UIWebView中限制);
  3. 支持了更多的HTML5特性;
  4. 高达60fps的滚动刷新率以及内置手势;
  5. 将UIWebViewDelegate与UIWebView重构成了14类与3个协议(查看苹果官方文档);

然后从以下几个方面说下WKWebView的基本用法:

  1. 加载网页
  2. 加载的状态回调
  3. 新的WKUIDelegate协议
  4. 动态加载并运行JS代码
  5. webView 执行JS代码
  6. JS调用App注册过的方法

一、加载网页

加载网页或HTML代码的方式与UIWebView相同,代码示例如下:

 WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];
[self.view addSubview:webView];

二、加载的状态回调 (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;
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

三、新的WKUIDelegate协议

这个协议主要用于WKWebView处理web界面的三种提示框(警告框、确认框、输入框),下面是警告框的例子:

/**
 *  web界面中有弹出警告框时调用
 *
 *  @param webView           实现该代理的webview
 *  @param message           警告框中的内容
 *  @param frame             主窗口
 *  @param completionHandler 警告框消失调用
 */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void (^)())completionHandler;

四、动态加载并运行JS代码

用于在客户端内部加入JS代码,并执行,示例如下:

// 图片缩放的js代码
NSString *js = @"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '张图');";
// 根据JS字符串初始化WKUserScript对象
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
// 根据生成的WKUserScript对象,初始化WKWebViewConfiguration
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:script];
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[_webView loadHTMLString:@"<head></head><imgea src='http://www.nsu.edu.cn/v/2014v3/img/background/3.jpg' />"baseURL:nil];
[self.view addSubview:_webView];

五、webView 执行JS代码

用户调用用JS写过的代码,一般指服务端开发的:

//javaScriptString是JS方法名,completionHandler是异步回调block
[self.webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];

六、JS调用App注册过的方法

WKWebView里面注册供JS调用的方法,是通过WKUserContentController类下面的方法:

- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

scriptMessageHandler是代理回调,JS调用name方法后,OC会调用scriptMessageHandler指定的对象。

JS在调用OC注册方法的时候要用下面的方式:

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

注意,name(方法名)是放在中间的,messageBody只能是一个对象,如果要传多个值,需要封装成数组,或者字典。整个示例如下:

//OC注册供JS调用的方法
[[_webView configuration].userContentController addScriptMessageHandler:self name:@"closeMe"];

//OC在JS调用方法做的处理
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    NSLog(@"JS 调用了 %@ 方法,传回参数 %@",message.name,message.body);
}

//JS调用
    window.webkit.messageHandlers.closeMe.postMessage(null);

如果你在selfdealloc打个断点,会发现self没有释放!这显然是不行的!谷歌后看到一种解决方法,如下:

@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>

@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

思路是另外创建一个代理对象,然后通过代理对象回调指定的self

WKUserContentController *userContentController = [[WKUserContentController alloc] init];    
[userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"closeMe"];

运行代码,self释放了,WeakScriptMessageDelegate却没有释放啊啊啊!
还需在selfdealloc里面 添加这样一句代码:

[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"closeMe"];

OK,圆满解决问题!

目前,大多数App需要支持iOS7以上的版本,而WKWebView只在iOS8后才能用,所以需要一个兼容性方案,既iOS7下用UIWebView,iOS8后用WKWebView。这个库提供了这种兼容性方案:https://github.com/wangyangcc/IMYWebView

以上部分内容参考自:http://www.brighttj.com/ios/ios-wkwebview-new-features-and-use.html

在本人播客中地址:http://www.wangyangdev.com/2015/11/13/使用WKWebView替换UIWebView/

相关文章

网友评论

  • RiberWang:swift版本的代理怎么写的
  • sankun:wkwebview post请求有什么好的解决方案嘛
    ch32053:@sankun 可以参考专题内的另外一篇文章 https://www.jianshu.com/p/866847bd139a
  • 36201ac0d926:我走断点,新写的那个代理类,里面的那个方法没有执行,是不是我少实现了一个代理啊
    ch32053:@吕方 那个代理方法?
  • 247a772391c7:请问楼主,我们这边需求是,点击按钮跳到一个活动抽web页面, 在抽奖页面还需要点击登录,再打开另一个登录界面,在登录界面登录后,才能点击抽奖。现在需要我点击按钮跳到第一个抽奖web页面的时候就自动登录。我应该怎么做?小白求教
    247a772391c7:@ch32053 实现了,用的cookie自动登录,但是是基于UIWebView的。WKWebView,有时候有问题
    ch32053:@jx_soul 抱歉,看到的晚了,实现了么?
  • MrCoolHao:我使用wkwebview,有的手机加载正常,有的加载空白,报错Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service,请问下,我改如何修改
    elite_kai:@MrCoolHao 是的,我现在就是这个问题,坑死,弄了两天没找到解决办法
    MrCoolHao:@elite_kai 没,一直在赶项目,这个问题排到最后修改了。而且我又发现一个问题,在iOS11上大部分手机都只能加载出来一部分,然后下边全是白屏,监听方法一直再走
    elite_kai:这个问题解决了吗?出现这种情况,页面就会白屏
  • 6cddee706b65:你好,如果用WKWebView如何获取内容呢,我在self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; 这段代码 直接崩溃了
    csqingyang:在 WKWebView 中继续使用 JSContext 是要做什么呢?
  • 经文纬武:楼主遇到过这个问题么,怎么解决 ? Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service
    透亮心情:@花下眠 我直接改成webview了
    elite_kai:这个问题解决了吗?出现这种情况,页面就会白屏
  • 经文纬武:请问 [userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"closeMe"]; 这个方法是写在当前控制器的viewDidload方法中么。为什么会报黄色警告。而且无效
  • 经文纬武:请问为什么我在注册JS时会报一个警告。

    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    [userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:_webView] name:@"actionLimitedListItem"];

    这是我在当前控制器注册的方法。但是不管用啊。

    然后和JS调我的方法。我这儿也收不到。
  • 经文纬武:之前用到是UIWebview 和JS交互的。现在想换成WKWebview。 是不是JS那边所有的调我的方法都要更改。我试了。怎么JS调不了我的方法啊
  • 秋雨无痕:还是有一些兼容问题cookie
  • 鹰眼米霍克:好东西
  • 遇见Miu:是WebKit而不是WekKit,作者打错了吧?
    ch32053:@小熊de忧郁 是的,多谢指正,我改下
  • LD_左岸:WKWebView loadHTMLString 加载出来的页面和原网页不一致 具体是缩小了 用UIWebView加载出来的页面是好的 大神知道咋回事吗
    coderAndyLi:亲,知道为什么了吗,我也遇到同样的问题了
  • daa47d66949b:学习~~~~
  • 32dbcf6aabbc:学习了,这里mark下,在使用WKWebView的时候,不能像UIWebView一样直接获取contentSize,需要使用KVO监听size变化,才能正确获取,不然一直都是0,自己踩的坑,希望也能帮到其他人
    无夜之星辰:使用RAC获取WKWebView里scrollView的高度,为什么第一次进入页面的时候不能正确获取?比实际内容高度大很多。
    EvanJq:webView.evaluateJavaScript("document.body.offsetHeight") { (offsetHeight, error) in
    if let height = offsetHeight as? CGFloat {
    }
    }
    ch32053:@泡小泡 :+1:
  • Maj_sunshine:有什么办法能让WKWebview中的其中一个按钮点击后不再继续跳转呢 求助:sweat:
    ch32053:@71b03c7180a1 找到辨别这个按钮的标示,然后在响应方法里屏蔽
  • 蚂蚁977:请问,js调用wkwebview的方法,怎样返回一个对象给js?
  • 闻醉山清风:楼主,我添加了[configuration.userContentController addScriptMessageHandler:self name:@"login"];
    但是却没有执行这个方法- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message

    我把web页面做成本地html文件,然后打开就可以。。同样的代码,用网络加载就不行。。能否请教是为什么呢?
  • 张不二01:你好,问一下,四、动态加载并运行JS代码 和 五、webView 执行JS代码两个的差别在什么地方,如果不是在viewdidload方法中调用,为什么五可以加载运行,四里面的方法就是没有效果呢?

  • 潇湘候晨雪:可以的
  • 8ca16feb9ff9://javaScriptString是JS方法名,completionHandler是异步回调block
    [self.webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];这个方法在哪调用..
    8ca16feb9ff9:- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler{}在这里面吗?
  • 宁静1致远:替换了是不是网页端代码需要修改呢?js端写window.webkit.messageHandlers.<name>.postMessage(<messageBody>)了oc端的代理才会走吗?我想把webView换掉,但是代理方法(- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message)不走,好蛋疼,是要前端配合修改吗?
    宁静1致远:@_超 网页端要配合修改,不修改我们做不到
    超_iOS:请问解决了么?
  • 1b05ef2b4848:请教下楼主 咱们用wkwebview的这个代理方法 接受前端的调用
    “- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message”
    比如说前端想跟客户端获取用户信息的话 客户端接收到信息之后 该怎么返回相应的信息呢?
    之前uiwebview 我们可以直接retuan相应的信息
    哄哄的薇薇:"之前uiwebview 我们可以直接retuan相应的信息",请教下,你们之前是调用UIWebView的哪个方法直接return的啊?
    1b05ef2b4848:@wangyangyang 最近遇到了点坑 貌似真的只能这样回调js了 那样是不是意味着 前端每调用一次客户端 都要配备相应的回调给客户端去调用 囧
    另外有个小问题 就是 前端通过这个代理传参数给客户端,是不是只能传一个 因为我尝试在前端传两个参数,中间用逗号隔开 客户端获取到的 只有第一个参数 :cold_sweat:
    ch32053:@Sue_moonlight 目前我采用的是调用js方法,:joy:
  • 始于__初见:使用WKWebview 控制台输出
    Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service
    Could not signal service com.apple.WebKit.Networking: 113: Could not find specified service
    有遇到么
    elite_kai:这个问题解决了吗?出现这种情况,页面就会白屏
  • 我叫赵小贱:WKWebView加载网页的时候,需要加载session 怎么办?
    ch32053:@我叫赵小贱 我们是通过在地址上拼cookie + 设置UserAgent的方式
  • 3040ba0de5d0:请问一个问题 以前uiwebview的时候 前端调用客户端的方法 是不是在wkwebview都不能用了,都要全部替换成 调用window.webkit?
    ch32053:@3040ba0de5d0 是的,用WKWebView的通过window.webkit调用
  • 5b9af1f92331:请教一个问题:
    UIWebView 中 可以通过JS调用OC得到OC的返回值,可用于JS的一些判断使用,
    MKWebView 该怎样做到这个效果呢?
    ch32053:@long1011 MKWebView 可以 JS调用OC方法,OC方法执行完后,再调用JS的方法返回结果,你最终采用什么样的解决方法?
  • 279532b6e067:要在合适的位置调用
    - (void)removeScriptMessageHandlerForName:(NSString *)name
    来释放 id<WKScriptMessageHandler>
  • 4a02187e2078:很及时 正需要 感谢分享
  • Sephiroth_Ma:好,不错哦
  • 小饼干是只松鼠:鼓励鼓励鼓励
  • 28bb64fffadd:请问webview能根据屏幕的尺寸自动适应嘛?就像 webView.scalesPageToFit = YES;一样,怎么解决呢 :smiley:
  • minjing_lin:楼主用心了,果断收藏
  • 喵小萌公主:多谢分享,太给力了
  • Link913:作者你好,IMYWebView使用方法是直接把github上的拖进项目去吗
    ch32053:@SkyHarute 是的
  • Jixin:这篇文章必须赞,第六条 JS调用App注册过的方法中关于使用- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;导致self无法释放的解决方案完美的解决了我的问题。我的ViewController无法释放,找了很久都没有找到原因,今天终于解决了,十分感谢!
    ch32053:@Jixin :blush: 多谢支持!
  • ch32053:没有呢,你是这样调用原生方法么?window.webkit.messageHandlers.(oc method name).postMessage()
  • 敖老黑:楼主有没有遇到过网页调用大量原声方法时,会导致白屏,只有刷新才能恢复
  • 泥塔猫:window.webkit.messageHandlers.responesToPhone.postMessage()

    Cannot read property 'messageHandlers' of undefined
    遇到这个问题,虽然iOS端的确能接收到信息,但是JS会因为这个问题而停止继续执行,请问应该怎么改啊。
    279532b6e067:@wangyangyang 是不是应该在WKUserContentController addScriptMessageHandler:name 方法调用完后,在前端webkit对象才会有 property 'messageHandlers' ,猜测 . 楼主后来找到原因了吗
    ch32053:@wangyangyang 你们的前端不知道么
    ch32053:@泥塔猫 这个应该是前端调用问题
  • 水三叶的刷题日记:使用你的库加载html文本的时候崩溃 无法使用。我是用的loadHTMLString方法 9系统
    水三叶的刷题日记:@wangyangyang 原生的可以 但是就是占用内存过高 才想切换wkwebview 断言在你那个loadHTML的方法实现里 控制台没有输出
    ch32053:崩溃提示什么?用原生uiwebview能正常加载么
  • 不明之人:你的代码是怎么做到无缝切换uiwebview?
    ch32053:@不明之人 根据系统版本判断
  • 不明之人:不错,期待很多关于webview的内容
    ch32053:@不明之人 多谢支持
  • 南烟客777:你这个我要添加web加载进度条还真不知道怎么操作,你自己这个还没补充完善,这个肯定是趋势的,大神
    南烟客777:@wangyangyang 嗯嗯,在gitub找到了
    ch32053:@荆天明丶 网上有第三方控件的
  • Dev_hell03W:不错不错
    ch32053:@Dev_hell03W 多谢支持
  • 栋柠柒:收藏
  • qBryant:正好用到了!多谢!
  • 罗同学_:请问下有没有好的解决缓存的方法
    e7410bc0f169:@单手两万行无bug 同问
  • JamesCaiLee:如果你在self的dealloc打个断点,会发现self没有释放!这显然是不行的
    ==>怎么看出self没有释放
    ch32053:@JamesCaiLee 类的dealloc 方法没调用,就是没释放
  • _YZG_:问下www.wangyagndev.com是怎么搭建的啊,求教,用的什么
    ch32053:@_YZG_ 我博客里面有搭建的教程
  • b01c913b01b1:window.webkit.messageHandlers.<name>.postMessage(<messageBody>)这句话是什么意思,这歌window是什么?
    ch32053:@光影色 前端开发里面的,表示当前窗口对象
    ch32053:@光影色 JS 里面的,应该是 获取当前窗口对象
  • c7e21c9a20c5:楼主你知道WKWebview怎么自适应屏幕吗?UIWebview上有scalespagetofit属性,但是WKWebView里面没有这个属性啊
    ch32053:@redtulip 可能吧,多谢提醒
    c7e21c9a20c5:@wangyangyang 是不是WKWebview内部已经对屏幕的适应进行了处理?我之前遇到的问题发现是automaticallyAdjustsScrollViewInsets没有进行设置,并不是WKWebview的问题。
    ch32053:@redtulip 暂时没找到行之有效的的方法,看看前端能处理不
  • 南风_001:首次进来是uiwebview 再次进来就是wkwebview 了 这是不是个bug
    f1badf52f473:那是因为你没有导入WebKit库,第一次失败会载入该库
    ch32053:@秦小风 初始化的时候,`usingUIWebView` 要设置成`NO`
    ch32053:@秦小风_01 在iOS 8下面?
  • ryugaku:马克
  • ryugaku:马克
  • 海天一角:这儿OC如何传参数给JS?
    [self.webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
    ch32053:这种相当于加载一个请求,我是说直接调用 前端 写的JS方法传值
    缺舟:我用这种方式 传不过去啊 后台总是接收为null

    NSString *url=[dic valueForKey:@"url"];
    // 设置请求路径和头信息
    NSMutableURLRequest* requestShare = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0];
    // 请求方式
    [requestShare setHTTPMethod: @"POST"];
    // 设参数
    NSString *bodyShare = [NSString stringWithFormat: @"orgCode=%@&currTime=%@", @"q123",@"2016-08-01"];
    // 添加参数
    [requestShare setHTTPBody: [bodyShare dataUsingEncoding: NSUTF8StringEncoding]];
    // 、加载网页
    [self.webView loadRequest:requestShare];
    ch32053:@海天一角 把方法和参数拼装成字符串,类似这样的,[NSString stringWithFormat:@"setImage('%@');",imagePath]
  • Jason_逆:请问,WKWeb加载本体html字体,为什么有个捏合手势会把视图缩放的。。。能关闭的吗
    ch32053:@600a348afc84 没碰到过你这种情况,WKWebView的contentSize 单独设置过么
  • Kevinz:不错,现在网页用的非常多
    ch32053:@Kevinz 多谢支持
  • 小樊:请问下
    如何修改WKWebView中的字体大小呢?
    05928c0c7c63:@小樊 你是加载本地数据还是网络请求来的
    ch32053:@小樊 这得前端开发研究了,我没弄过这样的需求,微信有这个功能
    ch32053:@小樊 这个要前段开发修改吧
  • ch32053:在iOS7下添加`JavaScriptCore`库,获取`JSContext`对象,然后添加自定义方法,JS那边就能调用了
  • 40b54b1bfddf:IMYWebView 下如果是在ios7 ,js怎么调用oc方法?好像并没有给出呢。
    SDBridge:可以下载最新版:pod 'WebViewJavascriptBridge', '~> 6.0.2'
  • 6afb6b3c28f9:wkwebview可以做手机上面的网页登录吗
    ch32053:@简了个简 这个不太清楚,要和前端开发沟通下
    Sense王旭明:问下楼主 H5里的localstorage事件, 即window.addEventListener('storage', myStorage_handler, false); 在UIWebView 和 WKWebView 中无效 是怎么回事??
    ch32053:@6afb6b3c28f9 可以啊,需要注意的是wkwebview只支持iOS 8之后
  • 花前月下:学习了。
    ch32053:@花前月下 多谢支持
  • lovedrose:mark
    ch32053:@lovedrose 多谢支持
  • SawyerZh:就这以后用
    ch32053:@天路客 多谢支持
  • 2ca64f0f0373:多谢分享
    ch32053:@loverk 多谢支持
  • 慕云少年:赞
    ch32053:@HxLemon 多谢支持
  • DanDanC:要等到iOS 7不适配了,才能用吧!
    WenJim:可以根据系统版本来适配
    ailanhou:@DanDanC 可以做兼容
    ch32053:@DanDanC 文章最下面有说,现在可以用兼容模式,iOS7用UIelwebview ,iOS9用wkwebview

本文标题:使用WKWebView替换UIWebView

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