使用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,自己踩的坑,希望也能帮到其他人
        Lol刀妹:使用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