怎么将一个数组的内容放到tableview高度和即将出现的cell中


一.使用约束(无需计算)

原理:cell嘚子控件通过垂直方向的约束将cell撑开


每个cell由三部分构成:顶部一个30px红色view,中间一个高度自适应内容的label底部一个30px的绿色view。
如果我们想让cell高度自适应内容可以这样设置约束:

cell里UI搭建的代码:

  • label的宽确定、高不设置,可以实现label的高度自适应内容
  • 绿色view和cell的底部对齐是撑开cell的关鍵所在。

二.使用frame(自己计算高度)

关于cell的高度你可以把它看做两个部分:

还是拿上面那个cell来说,它高度固定的部分是顶部红色view和底部绿色view;高度不固定的是label而我们要计算的,就是高度不固定的那部分

cell的高度就是高度固定部分(红色view的高度+绿色view的高度)与高度不固定部分(label嘚高度)的和。

三.如何提前计算高度

滚动tableview高度和即将出现时cell高度那个代理会不停回调,所以需要优化

cell的高度由cell的内容决定,cell的内容由cell的model決定。也就是说当我们获取到tableview高度和即将出现的数据源时,其实就已经可以计算出所有cell的高度了

1.将计算cell高度那段代码抽离出来

我写了個cell的类方法来计算cell高度:

2.在获取到tableview高度和即将出现数据源时计算cell高度并放到一个数组

// 将高度放进高度数组

3.需要cell高度时直接在高度数组里获取即可:

实现cell高度提前计算只需一个数组及几行代码,逻辑也非常简单然而带来的优化效果却是非常显著的,我自己测试的时候已深有感触
对于不只一个组的tableview高度和即将出现,需要二维数组或字典注意变通。


去掉了高度缓存数组给model添加了一个cellHeight属性:

拿到数据的时候僦将高度算好:

需要高度的时候直接从model中取:

  • 今天写了一个需求切换城市部分,宽度是93但是当城市变成四个字之后会挤压用mas_greaterThanOrEqu...

  • 老规矩先上效果图 新增吸顶和Fragment配合 GIF图有点不清楚,再上两张截图 项目地址:...

  • vivo商店在下载应用的时候底部有一个圆角矩形的下载进度条,中间有一个進度文字而且进度和文字交汇的时候,交汇部...

  • Xcode 12.2 环境跑真机一直卡在启动图,而且是突发的之前也没有出现过。 我的解决步骤如下: (每一步都...

  • 在地铁中刷抖音由于网络不通畅加载很慢,抖音会加载一个加载动画感觉很有意思,于是分析了一下自己写了Demo,实...

}
  • 不同iOS系统在高度计算上的差异

上媔的代码指定了一个所有 cell 都是 88 高度的 UItableview高度和即将出现对于定高需求的表格,强烈建议使用这种(而非下面的)方式保证不必要的高度计算和调用rowHeight属性的默认值是 44,所以一个空的 UItableview高度和即将出现 显示成那个样子

需要注意的是,实现了这个方法后rowHeight 的设置将无效。所以這个方法适用于具有多种 cell 高度的 UItableview高度和即将出现。

这个属性 iOS7 就出现了 文档是这么描述它的作用的:

在一开始并不知道自己会被填充多少內容,于是询问 data source 个数和创建 cell同时询问 delegate 这些 cell 应该显示的高度,这就造成它在加载的时候浪费了多余的计算在屏幕外边的 cell 上和上面的 rowHeight 很类姒,设置这个估算高度有两种方法: 

有所不同的是即使面对种类不同的 cell,我们依然可以使用简单的 estimatedRowHeight 属性赋值只要整体估算值接近就可鉯,比如大概有一半 cell 高度是 44 一半 cell 高度是 88, 那就可以估算一个 66基本符合预期。 

说完了估算高度的基本使用可以开始吐槽了: 

  1. 设置估算高度后,contentSize.height 根据“cell估算值 x cell个数”计算这就导致滚动条的大小处于不稳定的状态,contentSize 会随着滚动从估算高度慢慢替换成真实高度肉眼可见滚動条突然变化甚至“跳跃”。
  2. 若是有设计不好的下拉刷新或上拉加载控件或是 KVO 了 contentSize 或 contentOffset 属性,有可能使表格滑动时跳动
  3. 估算高度设计初衷昰好的,让加载速度更快那凭啥要去侵害滑动的流畅性呢,用户可能对进入页面时多零点几秒加载时间感觉不大但是滑动时实时计算高度带来的卡顿是明显能体验到的,个人觉得还不如一开始都算好了呢(iOS8更过分即使都算好了也会边划边计算)

具有动态高度内容的 cell 一矗是个头疼的问题,比如聊天气泡的 cell frame 布局时代通常是用数据内容反算高度:

各种魔法 margin 加上耦合了屏幕宽度。

cell它还要求使用者对约束设置的比较熟练,要保证 contentView 内部上下左右所有方向都有约束支撑设置不合理的话计算的高度就成了0。 

宽度的累加才能确定这个问题好像到 iOS8 財能够自动解决(不过我们找到了解决方案)

这个特性首先要求是 iOS8,要是最低支持的系统版本小于8的话还得针对老版本单写套老式的算高(囧),不过用的 API 到不是新面孔:

这里又不得不吐槽了自动计算 rowHeight 跟 estimatedRowHeight 到底是有什么仇,如果不加上估算高度的设置自动算高就失效了- -

  1. 這个自动算高在 push 到下一个页面或者转屏时会出现高度特别诡异的情况,不过现在的版本修复了 
  2. 求一个能让最低支持 iOS8 的公司- -

相同的代码在 iOS7 囷 iOS8 上滑动顺畅程度完全不同,iOS8 莫名奇妙的卡很大一部分原因是 iOS8 上的算高机制大不相同,这是我做的小测试: 

研究后发现这么多次额外计算有下面的原因:

  1. iOS7 计算高度后有”缓存“机制不会重复计算;而 iOS8 不论何时都会重新计算 cell 高度

iOS8 把高度计算搞成这个样子,从 WWDC 也倒是能找到點解释cell 被认为随时都可能改变高度(如从设置中调整动态字体大小),所以每次滑动出来后都要重新计算高度 

说了这么多,究竟有没囿既能省去算高烦恼又能保证顺畅的滑动,还能支持 iOS6+ 的一站式解决方案呢


写完上面的代码后,你就已经使用到了: 

  • 计算出的高度会自動进行缓存所以滑动时每个 cell 真正的高度计算只会发生一次,后面的高度询问都会命中缓存减少了非常可观的多余计算。
  • cell 时[0:0] ~ [0:4] 的高度缓存不受影响,而 [0:5] 后面所有的缓存值都向前移动一个位置自动缓存失效机制对 UItableview高度和即将出现 的 9 个公有 API 都进行了分别的处理,以保证没有┅次多余的高度计算 
  • 预缓存机制将在 UItableview高度和即将出现 没有滑动的空闲时刻执行,计算和缓存那些还没有显示到屏幕中的 cell整个缓存过程唍全没有感知,这使得完整列表的高度计算既没有发生在加载时又没有发生在滑动时,同时保证了加载速度和滑动流畅性下文会着重講下这块的实现原理。

我们在设计这个工具的 API 时斟酌了非常长的时间既要保证功能的强大,也要保证接口的精简一行调用背后隐藏着佷多功能。

这一套缓存机制能对滑动起多大影响呢除了肉眼能明显的感知到外,我还做了个小测试:

未使用缓存API、未使用估算共花费 877 ms:

使用缓存API、开启估算,共花费 77 ms:

测试数据的精度先不管从量级上就差了一个数量级,说实话自己也没想到差距有这么大- - 

同时工具也順手解决了-preferredMaxLayoutWidth的问题,在计算高度前向 contentView 加了一条和 table view 宽度相同的宽度约束强行让 contentView 内部的控件知道了自己父 view 的宽度,再反算自己被外界约束的寬度破除“鸡生蛋蛋生鸡”的问题,这里比较 tricky就不展开说了。下面说说利用


FDTemplateLayoutCell 的高度预缓存是一个优化功能它要求页面处于空闲状态時才执行计算,当用户正在滑动列表时显然不应该执行计算任务影响滑动体验
一般来说,这个功能要耦合 UItableview高度和即将出现 的滑动状态才荇但这种实现十分不优雅且可能破坏外部的 delegate 结构,但好在我们还有RunLoop这个工具了解它的运行机制后,可以用很简单的代码实现上面的功能 

  1. RunLoop即将进入休眠状态
  2. RunLoop即将从休眠状态被事件唤醒

因为“预缓存高度”的任务需要在最无感知的时刻进行,所以应该同时滿足:

  1. 当这一次 RunLoop 迭代处理完成了所有事件马上要休眠时

使用 CF 的带 block 版本的注册函数可以让代码更简洁: 

在其中的 TODO 位置,就可以开始任务的收集和分发了当然,不能忘记适时的移除这个 observer 

假设列表有 20 个 cell加载后展示了前 5 个,那么开启估算后 table view 只计算了这 5 个的高度此时剩下 15 个就是“预缓存”的任务,而我们并不希望这 15 个计算任务在同一个 RunLoop 迭代中同步执行这样会卡顿 UI,所以应该把它们分别分解到 15 個 RunLoop 迭代中执行这时就需要手动向 RunLoop

这个方法将创建一个 Source 0 任务,分发到指定线程的 RunLoop 中在给定的 Mode 下执行,若指定的 RunLoop 处于休眠状态则唤醒它處理事件,简单来说就是“睡你xx起来嗨!”

于是,我们用一个可变数组装载当前所有需要“预缓存”的 index path每个 RunLoopObserver 回调时都把第一个任务拿絀来分发:

这样,每个任务都被分配到下个“空闲” RunLoop 迭代中执行其间但凡有滑动事件开始,Mode 切换成 UITrackingRunLoopMode所有的“预缓存”任务的分发和执行嘟会自动暂定,最大程度保证滑动流畅 

PS: 预缓存功能因为下拉刷新的冲突和不明显的收益已经废弃


如果你觉得这个工具能帮得到你,整合箌工程也十分简单

写这篇文章时的最新版本为 1.2,去除了前一个版本的黑魔法增加了预缓存功能。

欢迎使用和支持这个工具有 bug 请随时反馈哦~

}

一个页面里面有三个tableview高度和即将出现,进行互相的联动,点击省显示对应的市,点擊市显示对应的区

1.创建三个数组的属性

注意:如果在初始化方法里使用self.view,此时还没有创建self.view,系统会自动調用loadView,创建一个self.view,从而改变VC的运行流程,所以我们只在初始化方法里初始化容器的数据部分,不创建视图

2.初始化并解析字典:

不要忘记代理人的设置和签订协议

 

 
 

4.必须出发的协议(1)返回行数

 

 

 
}

我要回帖

更多关于 tableview高度和即将出现 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信