UITableView嵌套WKWebView的那些坑
最近项目中遇到了一个需求,TableView中需要嵌套Web页面,我的解决办法是在系统的UITableViewCell中添加WKWebView。开发的过程中,遇到了些坑,写出来分享一下。
1.首先说一下WKWebView的代理方法中,页面加载完成后会走的代理方法,与UIWebView的页面加载完成代理方法一样。
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
- (void)webViewDidFinishLoad:(UIWebView *)webView;
这两个方法都是在web页面加载完成后才会走的代理方法。什么才算是加载完成呢?web页面中的所有元素都加载成功,包括图片、音频和视频等资源,都加载成功了,才算加载完成。如果通过这两个代理方法来计算cell的高度,你的整个tableview页面就会加载得很慢,至少要等web页面加载完成后才能计算高度重铺tableview。
2.WKWebView加载完成展示页面的时候,会让整个web页面自动适应屏幕的宽度。这样展示出来的效果就会很紧凑,页面内容会很小。
为了让web页面中的元素都适应屏幕的宽度显示,需要对WKWebView进行一下设置,原理是加载js代码,然后通过js来设置webview中的元素大小:
WKWebViewConfiguration *webConfig = [[WKWebViewConfiguration alloc] init];
WKUserContentController *wkController = [[WKUserContentController alloc] init];
webConfig.userContentController = wkController;
// 自适应屏幕宽度js
NSString *jsStr = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
WKUserScript *wkScript = [[WKUserScript alloc] initWithSource:jsStr injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
// 添加js调用
[wkUController addUserScript:wkScript];
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:webConfig];
如果加载的是HTML代码,图片的自适应可以通过下面一段CSS代码来设置:
NSString *html = @"HTML代码";
NSString *cssStr = @"<style type=\"text/css\">p{line-height: 24px!important;background-color: #fff;}img{max-width: 100%!important;}</style>";
NSString *htmlStr = [NSString stringWithFormat:@"<!DOCTYPE html><html><head><title>webview</title></head><body>%@%@</body></html>", cssStr, html];
[webView loadHTMLString:htmlStr baseURL:nil];
3.接下来说说重头戏,UITableViewCell嵌套WKWebView。
(1)cell的高度自适应(笔者用的是系统的cell,UITableViewCell)
cell的高度当然是根据webview的高度来设置的,所以首先要算出webview页面的高度。开篇就已经说过,webview加载完成的代理方法是web页面中的所有元素都加载成功,包括图片、音频和视频等资源,都加载成功了,才算加载完成。如果页面中的元素过多,网络图片过大,视频过大等,就会导致web页面加载卡顿。本身WKWebView这些资源是异步加载的,但是计算cell高度的时候是在这些资源都加载完成后才计算高度,WKWebView也就失去的异步加载的意义,所以整个tableview都会加载得很慢。
明白了这个道理,我个人推荐使用KVO来代替webview的代理方法。
// 对webView中的scrollView设置KVO
[_webView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
// KVO具体实现
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentSize"]) {
// 这里有两种方法获得webview高度,第一种就是通过JS代码来计算出高度,如下
// document.documentElement.scrollHeight
// document.body.offsetHeight
/*
[_webView evaluateJavaScript:@"document.body.offsetHeight" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
// 加20像素是为了预留出边缘,这里可以随意
CGFloat height = [result doubleValue] + 20;
// 设置一个高度属性,赋值后便于设置cell的高度
_webHeight = height;
// 设置cell上子视图的frame,主要是高度
_webView.frame = CGRectMake(0, 0, kScreenWidth, height);
_scrollView.frame = CGRectMake(0, 0, kScreenWidth, height);
_scrollView.contentSize =CGSizeMake(kScreenWidth, height);
// 获取了高度之后,要更新webview所在的cell,其他的cell就不用更新了,这样能更节省资源
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForRow:1 inSection:0], nil] withRowAnimation:UITableViewRowAnimationNone];
}];
*/
// 第二种方法就是直接使用监听的contentSize.height(这里要感谢[markss](http://www.jianshu.com/u/cc0bd919cb50)简友的评论提醒),但是与第一种方法不同的就是不能再加多余的高度了,那样会循环出发KVO,不断的增加高度直到crash。
UIScrollView *scrollView = (UIScrollView *)object;
CGFloat height = scrollView.contentSize.height;
_webHeight = height;
_webView.frame = CGRectMake(0, 0, kScreenWidth, height);
_scrollView.frame = CGRectMake(0, 0, kScreenWidth, height);
_scrollView.contentSize =CGSizeMake(kScreenWidth, height);
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForRow:1 inSection:0], nil] withRowAnimation:UITableViewRowAnimationNone];
}
}
// 别忘注销kvo
- (void)dealloc
{
[_webView.scrollView removeObserver:self forKeyPath:@"contentSize"];
}
这样设置后的webView直接addSubview到webCell.contentView上,页面是加载出来了,但是webView的高度并不能改变。
(2)然后笔者百度了一下,找到了一个方法。就是先将webView addSubview到一个scrollView上
[_scrollView addSubview:_webView];
然后再将这个scrollView addSubview到webCell.contentView上
[webCell.contentView addSubview:_scrollView];
这样webview的高度就可以改变了。但是这是什么原理,笔者还没搞清楚,希望懂的大神能给予热情而细心的指导。
(3)在刷新tableview刷新cell的时候,会重新走一遍所有的tableview代理方法,所以笔者建议尽量优化- (UITableViewCell )tableView:(UITableView )tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath中的代码,防止多次加载webview
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0:
{
ArticleHeaderTableViewCell *articleHeaderCell = [tableView dequeueReusableCellWithIdentifier:_articleHeaderIdentifier forIndexPath:indexPath];
return articleHeaderCell;
}
break;
case 1:
{
// 嵌套webivew的cell
UITableViewCell *webCell = [tableView dequeueReusableCellWithIdentifier:_articleWebIdentifier forIndexPath:indexPath];
[webCell.contentView addSubview:_scrollView];
return webCell;
}
break;
case 2:
{
ArticleCommentTableViewCell *articleCommentCell = [tableView dequeueReusableCellWithIdentifier:_articleCommentIdentifier forIndexPath:indexPath];
return articleCommentCell;
}
break;
default:
{
CommentsTableViewCell *commentsCell = [tableView dequeueReusableCellWithIdentifier:_commentsIdentifier forIndexPath:indexPath];
return commentsCell;
}
break;
}
}
总结:
1.在cell上嵌套webview之前,一定要在中间多加一层scrollview。
2.在计算cell高度的时候,不建议直接使用系统webview的代理方法,建议使用kvo。
3.进行减少- (UITableViewCell )tableView:(UITableView )tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath方法中对webview的操作,这样能减少webview的重复加载,提高性能和速度。
PS:每个人都有自己的想法,每个人的优化方式不同,笔者也是在摸索中尝试着,如果有说得不对不准确的地方,还请各位指正出来,大家共同探讨,互相学习。
代码下载地址:https://github.com/VonKeYuan/WKWebViewTest
作者:天蝎座的Von动必晒
链接:http://www.jianshu.com/p/44cfcf0fd538
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章:

深入了解line-height
1.定义 行高:两行文字baseline(基线)之间的距离 示意图: 2.为何line-height可以让单行文本垂直居中 其实并没有垂直居中,除非将font-size:0; 3.line-height的高度原理(可以先看看行内盒子的原理) * 行内元素的高度是lin…

实现一个简单的投票功能
实现一个简单的投票功能 最近项目中需要用到一个投票功能,当时觉得简单,向都没想就动手开始做,没想到走了不少弯路。 后来才发现,是想的太过简单了。来看看改进后的功能。 第一步:数据库设计 两个表:一个主…

Web漏洞扫描(四:知识点及错误总结)
WVS软件: WVS(Web Vulnerability Scanner)是一个自动化的Web应用程序安全测试工具,它可以扫描任何可通过Web浏览器访问的和遵循HTTP/HTTPS规则的Web站点和Web应用程序。适用于任何中小型和大型企业的内联网、外延网和面向客户、雇员、厂商和其它人员的W…
【VS开发】【智能语音处理】Windows下麦克风语音采集
简介 这是我很早以前的大学毕业设计,忽然间找到贴出来以纪念自己的纯真年代...但是因为CSDN不给面子所以导致短短的一篇文章贴了足足7次..他老提时说文章超过了64K,老大,拜托,那是算上了里面的图片大小吧...:-( 本文简单介绍了声卡的工作原理 , 录音的原理以及数字音频的基本知…

iOS音频——AudioToolbox
一、前言 二、音频文件Audio File Services 三、音频文件转换Extended Audio File Services 四、音频流Audio File Stream Services 五、音频队列Audio Queue Services 一、前言 AudioToolbox提供的API主要是C 使用起来相对晦涩,针对本文提供了简单的代码示例减小学…

【WA】九度OJ题目1435:迷瘴
题目描述: 通过悬崖的yifenfei,又面临着幽谷的考验——幽谷周围瘴气弥漫,静的可怕,隐约可见地上堆满了骷髅。由于此处长年不见天日,导致空气中布满了毒素,一旦吸入体内,便会全身溃烂而死。幸好y…

云端应用SQL注入攻击
实验目的及要求: 完成VMware Workstations14平台安装,会应用相关操作;完成Windows server2008R2操作系统及Kali Linux操作系统的安装;掌握SQLmap攻击工具的使用;使用SQLmap对目标站点进行渗透攻击; 实验环…

SPOJ375(树链剖分)
题目:Query on a tree 题意:给定一棵树,告诉了每条边的权值,然后给出两种操作: (1)把第i条边的权值改为val (2)询问a,b路径上权值最大的边 分析:本题与HDU3966差不多,区别就是&#…

简单的python服务器程序
一个接受telnet输入的服务器端小程序 #!/usr/local/bin/python3.5 #coding:utf-8 import sockethost port 51423s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(1)print(S…

iOS:一句代码实现文本输入的限制
前言 实际开发中,往往需要处理UITextView、UITextField输入的限制。比如输入必须是价格格式(一个小数点、小数点后面最多两位);输入最大长度限制;对输入内容的实时回调。处理这些的时候,我们通常需要做一些…

Linux操作系统(一:基本操作)
1、创建一个以自己姓名拼音简写一致的用户名( useradd -d /home/abc abc) 2、在linux中使用打印命令,在命令行中输入“当前用户名Hello world !” 3、显示现在天年月日,显示后一天的日期,显示上一月日期,显…

Core Text 学习笔记-基础
前言 最近在学习YYKit框架,看到关于CoreText相关的知识的时候感到非常吃力,于是乎就恶补了一下Core Text相关的基础知识。 Glyphs(字形) 字符的图形形式, 则是文字中字母 (character) 的视觉表现。(字形&am…

虚拟机下CentOS 6.5配置IP地址的三种方法
实验软件环境:虚拟机Vmware Workstation10.0 、CentOS 6.5 32位 1、自动获取IP地址 虚拟机使用桥接模式,相当于连接到物理机的网络里,物理机网络有DHCP服务器自动分配IP地址。 dhclient 自动获取ip地址命令 ifconfig 查询系统里网卡信息&…

GIS 相关知识扫盲
1、什么是GIS GIS:地理信息系统,它是一种特定的十分重要的空间信息系统。它是在计算机硬、软件系统支持下,对整个或部分地球表层(包括大气层)空间中的有关地理分布数据进行采集、储存、管理、运算、分析、显示和描述的技术系统。2…

Linux操作系统(二:shell脚本)
练习一:编写shell脚本,计算1-100的和; 练习二:将一目录下所有的文件的扩展名改为bak 练习三:写一个脚本,统计。/etc/ 目录下共有多少个目录文件 练习四:写一个脚本,依次向/etc/passw…

用OpenGLES实现yuv420p视频播放界面
背景 例子TFLive这个项目里,是我按着ijkPlayer写的直播播放器,要运行需要编译ffmpeg的库,网盘里存了一份, 提取码:vjce。OpenGL ES播放相关的在在OpenGLES的文件夹里。 learnOpenGL学到会使用纹理就可以了。 播放视频,就是把画面一…

C++类的静态成员详细讲解
在C中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。 静态成员的定义或声明要…

jenkins2 multibranch
通过multibranch类型的pipeline job使得对于多个branch的支持更加简单。只需要创建一个multibranch job,jenkins将自动地为所有的branch创建job。 文章来自:http://www.ciandcd.com文中的代码来自可以从github下载: https://github.com/ciand…

Nagios的安装和基本配置(一:知识点总结及环境准备)
实验目的及要求 掌握Nagios监控的基本使用;掌握Nagios监控服务的搭建和配置; 实验环境: 1、满足实验要求的PC端; Host-name OS IP sofaware Nagios-server Centos7 192.168.1.119 Apache,php,Nagios,Nagios-plguins Nag…

浅谈Android四大组件之Service
一:Service简介 Android开发中,当需要创建在后台运行的程序的时候,就要使用到Service。 1:Service(服务)是一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service,并且当用户切…

使用 fastlane 实现 iOS 持续集成(二)
本文接上篇文章主要说下怎样使用 fastlane 上传到fir和蒲公英,下面先介绍下 plugin 命令。 plugin命令介绍: 列出所有可用插件 fastlane search_plugins 搜索指定名称的插件: fastlane search_plugins [query] 添加插件: fastlane add_plugin [name] 安装插件: fast…

Nagios的安装和基本配置(二:Nagios-Server的安装)
任务二、Nagios-server的安装 2.1、创建Nagios用户和组 注: #useradd Nagios -s /bin/nologin #groundadd nagcmd #usermod -a -G nagcmd Nagios #usermod -a G nagcmd apache 2.2、安装Nagios 2.2.1、上传软件包至操作系统中; 2.2.2、解压软件并…

shell编程-正则表达式
1.正则表达式是什么 它主要用于字符串的模式分割,匹配,查找及替换操作。 2、正则表达式与通配符 正则表达式用来在文件中匹配符合条件的字符串,正则包含匹配。grep,awk,sed等命令可以支持正则表达式。 通配符用来匹配符合条件的文件名&#x…
使用 CocoaPods 给微信集成 SDK 打印收发消息
推荐序 本文介绍的是一套逆向工具,可以在非越狱手机上给任意应用增加插件。在文末的示例中,作者拿微信举例,展示出在微信中打印收发消息的功能。 这套工具可以加快逆向开发的速度,其重签名思想也可以用于二次分发别人的应用。 其实…

数据库之子查询四(多重,表复制)
一、多重子查询 select teaID,teaName,age,sex,dept,professionfrom tteacherwhere dept(select dept from teaIDt103265)and profession(select professionfrom tteacherwhere teaIDt103265)这里的子查询就是为了从表中提取出有效信息参与外部查询二、create table 语句中子查…

Nagios的安装和基本配置(三:Nagios-Client的安装)
任务三、Nagios-Client的安装 3.1、关闭防火墙和selinux 注: #systemctl stop firewalld.service #systemctl disable firewalld.service #vi /etc/selinux/config 3.2、配置环境 #yum install gcc glibc-common -y #yum install gd gd-devel openssl openssl…

事件(待完成)
内容窗口 事件绑定 给整个浏览器 内容窗口区的事件绑定。 通过 document.documentElement或者document.body?似乎都可以。但最好是直接通过document document.addEventListener(mousemove,function () { });// 整个浏览器内容范围都将触发。拖动实现必用 转载…
iOS 模仿支付宝支付到账推送,播报钱数
最近申请了支付宝的二维码收钱码,其中支付宝有这么一个功能,就是,别人扫描你的二维码给你转账之后,收到钱会有一条语音推送,”支付宝到账 1000万“之类的推送消息,不管你的支付宝app有没有被杀死。 只要你的…

hdu - 4707 - Pet
题意:一棵N个结点(编号从0开始)的树,根结点为0,求到根结点的距离大于D的结点个数(0 < 测试组数T < 10, 0<N<100000, 0<D<N)。 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid4707…

Nagios的安装和基本配置(四:调试验证 错误总结)
任务四、调试验证 4.1、验证连通性 在/usr/local/Nagios/etc/nrpe.cfg文件中server的ip地址 #vi /usr/local/Nagios/etc/nrpe.cfg #重启nrpe #pkill nrpe #netstat -Intp #/usr/local/Nagios/bin/nrpe -d -c /usr/local/Nagios/etc/nrpe.cfg #在server主机做验证 #cd /…