WKWebView 使用和坑

2023-11-03

iOS8以后,苹果推出了新框架Wekkit,提供了替换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];
 NSUrl * url = [NSURL URLWithString:@"http://www.baidu.com"];
[webView loadRequest:[NSURLRequest requestWithURL:url]];
[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界面中有弹出alert时调用,对应不同的alert
    不同点在最后的block回调的参数,  可以无参数,Bool,和NSString 三种返回给JS.
   当然 记得给delegate 和遵守 WKUIDelegate 协议啊 .
 */

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
    
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    //    DLOG(@"msg = %@ frmae = %@",message,frame);
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = defaultText;
    }];
    [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];

}


​

四、动态加载并运行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写过的代码,一般是h5的小伙伴开发好的方法, iOS调用:

    NSString *jsFounction = [NSString stringWithFormat:@"iosToH5('%@')", @"原生调h5"];
    [self.webView evaluateJavaScript:jsFounction completionHandler:^(id object, NSError * _Nullable error) {
        NSLog(@"obj:%@---error:%@", object, error);
    }];

六、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);
}

// h5的代码中 , JS这样调用
    window.webkit.messageHandlers.closeMe.postMessage(null);


如果你在viewController的dealloc打个断点,会发现self没有释放!经过一番查找,找到了这一句,猜测就是userContentController强引用了self,
[[_webView configuration].userContentController addScriptMessageHandler:self name:@"closeMe"];

找到之后就好说了, 官方还提供了remove的方法, 在Nav pop的时候调用,果然没有了循环引用,
[userContent removeScriptMessageHandlerForName:@"closeMe"];

方法是有了,但是感觉写起来有点麻烦,如果只有一种交互还可以忍,但是交互多了,这么写太累了,而且,通过代理的方式进行的回调,userContentController: didReceiveScriptMessage: 写起来也不是很舒服,所有就想办法封装成block回调.这里不贴代码了 , 附上git地址 ,需要的话直接拖过去文件使用就行了, https://github.com/guochaoshun/WKWebViewWithH5

由于是ipad加载webView ,需要考虑转屏的问题, 所以页面缩放也得做.

UIWebView 有属性 scalespageToFit,设置为:YES,可以自动对页面进行缩放以适应屏幕
那么,WKWebView怎么做可以实现自动缩放网页比例 ? 

还是代理,还是协议...  

_wkWebView.scrollView.delegate = self; //   注意这里也有一个坑,如果这么写了的话, 需要在  dealloc (或者其他不用的敌法)中 把 代理置nil

//     self.webView.scrollView.delegate = nil ;  否则会崩溃
之前有引用到WKWebview,为使用方便将WKWebview设为了成员变量,然后又设置了该成员变量的scrollview的属性的代理为当前视图控制器,然后就出现了问题,每次push时候从新创建时候总会访问之前的内存,然后报错说访问了一块已经释放掉的内存,pop出栈的时候会崩溃,这样一直找不到问题的存在,后来才知道强引用了scrollview,代理释放不掉,所以会报错,解决办法,在dealloc函数或者viewwillappear等函数中将代理设为nil就解决了
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return nil;

坑一:获取不到WKWebView的高度

获取方法:在WKWebView加载成功的代理方法里获取WKWebView的UIScrollView的contentSize

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    self.webViewContentHeight = self.webView.scrollView.contentSize.height;
}

运行后,发现获取不到contentSize, 打印结果显示(width = 0, height =0).

解决办法:使用KVO监听WKWebView的contentSize


-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (!self.webView.isLoading) {
        if([keyPath isEqualToString:@"scrollView.contentSize"])
        {
            self.webViewContentHeight = self.webView.scrollView.contentSize.height;
            CGRect frame = self.webView.frame;
            frame.size.height = self.webViewContentHeight;
            self.webView.frame = frame;
            [self.webView sizeToFit];
        }
    }
}

Bingo! 完美取到WKWebView的height.

坑二:移除KVO的keypath时,程序crash。

- (void)dealloc {
    [self.webView removeObserver:self forKeyPath:@"scrollView.contentSize" context:nil];
}

这种情况通常出现在对同一个keypath进行两次remove,如父类中有一个kvo, 父类在dealloc的时候remove一次,子类dealloc的时候又remove一词。看到这里想必大家都已经知道解决思路了吧?那就是区分父类和子类的KVO,回头看一下发现addObserver和removeObserver中都有一个context参数,没错,这个参数就可以用来标记我们自己添加的KVO。

[self.webView addObserver:self forKeyPath:@"scrollView.contentSize" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"DJWebKitContext"];

- (void)dealloc {
    [self.webView removeObserver:self forKeyPath:@"scrollView.contentSize" context:@"DJWebKitContext"];
}

运行一下,退出页面的时候果然不会crash了

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

WKWebView 使用和坑 的相关文章

  • 从 iPhone 上传图像/音频到服务器无法发送大图像

    我不知道这是我的iPhone代码还是服务器的问题 我正在使用 NSURLConnection 将图像 音频从 iPhone 上传到服务器 如果图像和音频的大小小于 60KB 则可以正常上传 如果超过 60KB 我会从服务器收到 400 Ba
  • -[NSObject(NSObject) doesNotRecognizeSelector:] 调用时崩溃 -[ViewControllerprepareForSegue:sender:]

    我正在使用 Crashlytics 来检测我的应用程序中的崩溃 我偶尔会收到以下崩溃报告 关键崩溃点是 NSObject NSObject doesNotRecognizeSelector 和 MyViewController m 第 59
  • iOS 从另一个类更新 ViewController UILabel

    我是开发新手 一直在用头撞墙试图弄清楚这一点 我确信 我错过了一些愚蠢的东西 但在尝试了各种不同的解决方案后 我仍然无法得到结果我在寻找 我希望能够从另一个类更新 ViewController 中的 UILabel 这是一个我无法运行的小演
  • reloadData 调用 numberOfSections、numberOfRows,而不是 cellForRowAtIndexPath

    首先 如果格式不正确 我很抱歉 这是第一次这样做 我已经使用 stackoverflow 来寻求帮助很长时间了 它非常有帮助 谢谢大家 但这是我第一次发布自己的问题 这个问题已经被问过很多次了 但是当我调用 myTable reloadTa
  • 如何在 Objective C 中创建 json 字符串?

    我必须动态生成一个 json 字符串并需要发送到服务器 有谁知道如何使用NSJSONSerialization 下面是我的字符串 surveyid Survey1 responsetime dd mm yyyy hh mm ss locat
  • iOS 版 Google Analytics 中的线程崩溃

    使用适用于 iOS 版本 3 0 9 以及一般的 3 0 x 的 Google Analytics 库 我们看到很多像下面这样的崩溃 它们似乎是随机发生的 Exception Type SIGBUS Exception Codes BUS
  • Objective-C 2.0中的多线程问题

    我有我的主应用程序委托 其中包含一个返回对象的方法 该应用程序委托在主线程上运行 我还有一个在不同线程上运行的 NSOperation 除了希望有时能够在主线程上调用我的应用程序委托方法之外 我还需要从 NSOperation 线程中调用它
  • 对象序列化 - 从 C# 或 java 到 Objective C

    服务器端 C 或 java 客户端 Objective C 我需要一种在 C java 中序列化对象并在 Objective C 中反序列化它的方法 我是 Objective C 的新手 我想知道从哪里可以获得有关此问题的信息 Thanks
  • 使用 NSPredicate 来检测 NOT CONTAINS

    我放弃 我已经尝试了我能想象到的所有组合来检查一个字符串是否包含另一个字符串 这是描述我想要做的事情的直观语法示例 NSPredicate pPredicate NSPredicate predicateWithFormat NOT K C
  • 为什么 Objective-C 数组参数不使用冒号表示法?

    我目前正在从大牧场指南书中学习一些 Objective C 我的理解是 具有多个参数的方法使用冒号来分隔每个参数 但是在阅读有关创建数组的内容时 我发现了以下代码片段 NSArray dateList NSArray arrayWithOb
  • TabBarController:以不同方向定向视图

    我无法保持当前的观点方向 在下面的设置中 我能够将第一个视图控制器锁定为纵向 将第二个视图控制器锁定为横向或纵向 但是 当我向选项卡控制器添加第二个导航控制器 rootviewcontroller 时 整个项目中的所有视图都将变为横向和纵向
  • 使用自定义组件:子类 UIView 或 UIViewController?

    我正在研究 UISegmentedControl 的自定义实现 我想创建一个能够接收配置数据并从中获取类似于 UISegmentedControl 的自定义视图的组件 我开始对 UIView 进行子类化 我可以使用以下代码创建自定义 UIS
  • 在 Interface Builder 中启用/禁用 NSLayoutConstraints

    NSLayoutConstraint in iOS 8 0 has a BOOL属性称为active这使得动态禁用 启用所述布局约束变得容易 要为视图控制器创建第二个布局集 然后我可以以编程方式启用 禁用它 通过IBOutletCollec
  • 如何在 xcode 中从 nib 文件创建视图?

    我有以下代码来创建视图并将其放入滚动视图中以允许分页代码工作正常 但是我不能做的是从 nib 文件加载视图 换句话说 我想使用 initWithNibName 而不是 initWithFrame void createPageWithCol
  • UIBezierPath containsPoint:无法正常工作?(更新:超级视图中的触摸位置如何处理?)

    所以我有 2 个视图可以绘制贝塞尔路径然后返回路径 然后我需要检查路径是否包含我在以下帮助下执行此操作的点 path containsPoint currentObject position 它适用于其中一种观点 但不适用于另一种观点 一个
  • 志愿者匹配 API Objective C

    我正在使用 AFNetworking 对 VolunteerMatch API 执行 Web 请求 当我执行请求时 我收到 200 代码 但没有收到响应 典型的 VolunteerMatch 请求如下所示 GET api call acti
  • iOS 初学者:带有 3 个按钮的 UIAlertView 窗口 > 检查按下了什么按钮

    我有一个教程中的工作代码 但不完全理解它 情况 在我的 iPhone 应用程序中按下按钮后 将出现一个包含三个按钮的 AlertView 现在我想检查用户按下了什么按钮 教程中的代码 IBAction infoButtonPressed i
  • Objective-C 相当于 Java 枚举或“静态最终”对象

    我试图找到一个与 Java 枚举类型或 public static final 对象等效的 Objective C 例如 public enum MyEnum private String str private int val FOO f
  • FlurrySDK 与 cocoapods

    我正在尝试使用 Cocoapods 将 FlurrySDK 框架集成到我的应用程序中 正如我已经使用很多框架所做的那样 但由于某种原因 xcode 不断抛出此编译错误 Undefined symbols for architecture a
  • 如何使用 UIScrollView?

    我该如何使用UIScrollView 请给我一个带有一张滚动图像的简单示例 这将使您深入了解UIScrollView控制 学习 UIScrollView 的基础知识 https stackoverflow com questions 159

随机推荐

  • 字节对齐

    一 什么是字节对齐 为什么要对齐 现代计算机中内存空间都是按照byte划分的 从理论上讲似乎对任何类型的变量的访问可以从任何地址开始 但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问 这就需要各种类型数据按照一定的规则在空间上
  • Hbase解决ERROR: KeeperErrorCode = ConnectionLoss for /hbase/master报错

    1 在单机模式中 要先修改一个文件 usr local hbase conf hbase site xml hbase site xml内容
  • element ui + vue项目,修改el-divider默认样式

    修改el divider 垂直分割线的样式 以修改margin为例 其他样式改变同理
  • 实验2-动态规划编程题4. 01背包问题

    问题描述 给定一个容量为C的背包 现有n个物品 每个物品的体积分别为s1 s2 sn 价值分别为v1 v2 vn 每个物品只能放入一次 背包最多能装入价值为多少的物品 输入形式 输入的第1行包含2个整数C和n 分别表示背包容量和物品个数 接
  • gdb--设置断点的方法

    package utils import fmt github com gin gonic gin net http type Album struct ID string json id Title string json title A
  • 一个赛马算法

    原题 25匹马 5条跑道 怎样能用最快方式 得到最快的三匹马 假设每匹马的体力保持不变 速度固定 解法 堆排序 如下 package org algorithm search import java util ArrayList impor
  • 【vue】vue2 获取本地IP地址

    具体代码
  • 如何挖掘关键词

    挖掘关键词是SEO的基本功 借助的工具有 百度下拉框 百度相关搜索 搜搜问问 百度知道 百度推广助手 百度指数等 1 百度下拉框和相关搜索 通过下拉框和相关搜索搜集长尾词的方法是一级一级搜集 比如搜索SEO 然后再搜索SEO的下拉框里面的S
  • STM32F4 RTC-Alarm 的使用(RT-Thread操作系统)

    文章目录 1 工程的创建和配置 1 1 CubeMX 的配置 1 1 1 时钟源的选择 1 1 2 Debug 引脚配置 1 1 3 控制台串口的配置 1 1 4 RTC的配置 1 1 5 时钟树配置 1 1 6 代码生成 1 2 RT T
  • 合并多个文件到一个文件内

    package com caini psaer test import java io import java nio charset StandardCharsets import java util ArrayList import j
  • 快速排序实现(递归+非递归)

    快速排序代码 首先是划分算法 假设每次都以第一个元素作为枢轴值 进行一趟划分 int Partition int A int low int high int pivot A low while low lt high while low
  • java将office文件转换为pdf文件的三种方法

    方法1 poi读取doc itext生成pdf 实现最方便 效果最差 跨平台 方法2 jodconverter openOffice 一般格式实现效果还行 复杂格式容易有错位 跨平台 方法3 jacob msOfficeWord SaveA
  • win10 vmware虚拟机蓝屏怎么办 win10 vmware虚拟机蓝屏解决方法【详解】

    最近有朋友出现win10 vmware虚拟机蓝屏的情况应该怎么办 小伙伴们在使用vmware虚拟机出现了蓝屏现象的小伙伴们不用担心 小编翻阅各种资料后给大家带来两种虚拟机蓝屏的解决方法 想要解决此问题的小伙伴们快跟着小编往下看吧 win10
  • 总线(BUS)和总线操作

    1 什么是总线 答 总线是运算部件之间数据流通的公共通道 2 总线的作用 答 提高专用信号处理逻辑电路的运算能力和速度 3 总线与部件之间是怎么连接的 答 各运算部件和数据寄存器组是通过带控制端的三态门与总线相连接的 通过控制端口电平的高低
  • netlab在线助手(基于鼠标移动事件+窗体设计)

    随着老板每天查岗的频率越来越高 奈何自己是个十足的夜猫子 早晨的被窝就像一块磁铁牢牢的吸着我 俗话说 懒人也有勤劳的时候 那一定是在想怎么可以偷懒 哈哈哈哈 偷偷制作了一款在线助手 再也不用担心早上迟到了 还可以挂时长 美滋滋 废话不多说
  • JAVA中的四种JSON解析方式详解

    我们在日常开发中少不了和JSON数据打交道 那么我们来看看JAVA中常用的JSON解析方式 1 JSON官方 2 GSON 3 FastJSON 4 jackson JSON操作涉及到的类 public class Student priv
  • C语言的printf函数以从右到左的顺序输出,每个数据项可以进行算术但各自互不影响

    今天在一个网站上看到有个冒泡排序算法 最后的输出prinf输出函数如 printf c a i a i 突然记得在什么地方看过一种说法 C语言的输出是从右到左的 但具体却很模糊 下班回来之后就试了一下 代码如下 include
  • 指令大全(win+r)

    1 appwiz cpl 程序和功能 2 calc 启动计算器 3 certmgr msc 证书管理实 程序 4 charmap 启动字符映射表 5 chkdsk exe Chkdsk磁盘检查 管理员 份运 命令提 符 6 cleanmgr
  • 近期必读的7篇【医学图像分割】相关论文和代码(CVPR、AAAI)

    导读 最近小编推出CVPR2019图卷积网络相关论文 CVPR2019生成对抗网络相关视觉论文 可解释性 相关论文和代码 CVPR视觉目标跟踪相关论文 CVPR视觉问答相关论文 反响热烈 最近 医学图像分割这一新分割应用领域也广泛受关注 出
  • WKWebView 使用和坑

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