转:iOS开发之多种Cell高度自适应实现方案的UI流畅度分析
本篇博客的主题是关于UI操作流畅度优化的一篇博客,我们以TableView中填充多个根据内容自适应高度的Cell来作为本篇博客的使用场景。当然Cell高度的自适应网上的解决方案是铺天盖地呢,今天我们的重点不是如何讨论Cell高度的自适应,而是给出几种Cell高度自适应的解决方案,然后对比起UI流畅度,从而得出一些UI优化的一些常规做法。今天博客中主要用涉及的第三方库是YYKit和AsyncDisplayKit。
关于YYKit和AsyncDisplayKit这两个库,本篇博客只是简单的涉及到一些基本用法,主要是针对我们本篇博客的Demo来使用的,其中好多功能并未使用。因为之前在项目中没怎么使用过这两个框架,所以本篇博客就不着重介绍着两个第三方框架了,如果你对其感兴趣,Github上有你想要的内容,请自行搜索。废话少说,进入今天的主题。
一、总述
本篇博客主要给出了5种Cell自适应高度的解决方案,并对比了每种实现方案的流畅度。也可以说是从UI最不流畅的一种我们慢慢优化,从而实现了这5种解决方案。当然我们是观察屏幕的FPS来判断屏幕在操作时是否卡顿。关于对FPS的实时监测,我参考了YYKit-Demo中的做法,并将其单独提取了一个组件,便于我们项目的使用,关于这个提取的FPS组件,下方使用时会具体介绍。当然本篇博客所涉及的所有代码,依然会分享到Github上,文章后方会给出相应的链接,有需要的小伙伴请自行clone。
下方这个截图是我们今天demo的菜单列表页面,点击每个Cell都会跳转左边这个内容列表页面。不过每个Cell所对应的内容页面的Cell自适应高度的实现方式不同,我们在对其滑动操作时,可以根据下方这个FPS组件来观察屏幕的流畅度。当然,每个内容列表页的布局和显示内容都是相同的,不过不同的Cell自适应解决方案所对应的UI流畅度也是不同的。下面我们先大体的聊一下每种Cell自适应的实现方案。
- Autolayout + AutomaticDimension: 该解决方案对应着,下方第一个Cell, 点击该Cell进入的页面完全由AutoLayout进行布局,Cell自适应的高度也不用我们自己计算,而是使用系统提供的解决方案UITableViewAutomaticDimension来解决。当然,使用UITableViewAutomaticDimension要依赖于你添加的约束,稍后会介绍到。这种实现方案用起来简单,不过UI流畅度方面不太理想。当TableView快速滑动时,就会出现掉帧,卡的不要不要的。
- Autolayout + CountHeight: 这种解决方案依然是采用AutoLayout的方式来对Cell的内容进行布局,不过Cell的高度我们是自己计算的,当然我们这个计算Cell高度的过程是放在子线程中进行的,所以这种实现方式要优于第一种实现方式,稍后会详细介绍。
- FrameLayout + CountHeight: 为了进一步提高流畅度,我们采用了纯Frame布局,之前好像在哪儿看过,说Autolayout最终也是会被转换成Frame进行布局的,所以我们索性就使用Frame对整个Cell中的元素进行布局。当然Cell高度已经Cell中可变内容的高度都是在子线程中进行计算的,这也是优化很重要的一步。这种实现方式还是比较流畅的,可以作为折中的方案。
- YYKit + CountHeight: 这种解决方案用到了YYKit中的控件,并且使用Frame布局与Cell高度的计算。这种方式要由于上面的解决方案,比较YYKit中的一些控件做了优化。
- AsyncDisplayKit + CountHeight: 则是使用了AsyncDisplayKit中提供的相关Note代替系统的原生控件,这种实现方式是这5种实现方式中最为流畅的。稍后会详细介绍。
上面这五种实现方式将是下方介绍的具体内容,当然会涉及一些其他的技术实现细节。

二、博客所涉及的自定义工具介绍
在进入主题之前,先进行预热。先对本片博客中所涉及的一些小工具进行介绍。当然这些工具是自己封装的,是本篇博客中所涉Demo的基础,本部分将进行统一介绍,在使用时我们就一笔带过即可。
1.工具一:FPSDisplay
上述Demo中使用到了一个小的组件是FPSDisplay, 用于实时显示屏幕的刷新频率的。我们知道现在iPhone的FPS是60。也就是每秒刷新60帧,如果低于60帧的话那就是掉帧了,如果掉帧掉的多的话就会明显的看出卡顿。上述截图中右下方的黑色图标就是我们封装的FPSDisplay工具。当然该工具是参考着YYKit-Demo中所实现的,对其进行的简化和封装,将其提取成了一个单独的组件,便于在我们的应用中引入。
下方就是FPSDisplay引入并初始化的过程,下方是在AppDelegate中的didFinishLaunchingWithOptions中添加的。因为FPSDisplay是添加在KeyWindow上的,所以在FPSDisplay初始化时要保证你的App已经有了KeyWindow了。进行下方初始化后,在你的App的右下方就会出现一个图标来实时的显示FPS。

FPSDisplay的实现并不麻烦,主要是CADisplayLink的使用,将创建CADisplayLink创建的对象添加到MainRunLoop中,就可以以此来计算FPS了。下方是FPSDisplay的核心代码。在每次进行屏幕刷新时都会执行下方的tink方法,我们可以来计算1秒内刷新的次数,也就是所谓的FPS。代码比较简单,在此就不做过多的赘述了,详细的代码在Github上已经分享。

2.工具二:数据提供者
除了上述的FPSDislay工具外,我们还需要一个模块,那就是为Demo提供模拟数据的模块。因为我们没有网络模块,我们就模拟网络请求来生成数据,然后对数据进行处理生成Model。当然这个生成测试数据的过程没有用到主线程,为了不阻塞Main线程,我们需要将数据生成的部分在子线程中异步的执行。当然此处主要涉及多线程的东西。下方代码段就是数据提供者DataSupport的核心代码。
下方代码段主要用到了并行队列的异步执行,任务组的使用,已经任务锁的添加。下方首先创建了一个并行队列concurrentQueue和队列的任务组group,并且为了数据同步,我们使用信号量创建了一个任务锁lock。在for循环中我们异步的执行并行队列来创建我们需要的数据模型Model。每循环一次创建一个Model,为了Model数据的独立性,在创建Model时,我们要为其添加信号量同步锁。
当50条数据异步创建完毕后,我们需要将其提供给数据提供者的使用放,也就是在任务组中的任务都执行完毕后,会执行下方的notify方法。

在Model创建时,我们会对Model中可变的文字,也就是Cell中高度变化的内容的高度进行计算。当然该计算是在子线程中异步执行的。所以不会占用主线程的时间来计算Cell的高度以及Cell中可变文字的高度。我们Model中有两个字段就是来存储Cell的高度以及可变文本的高度的,如下所示。这样做的好处就是提高UI的流畅度。

3.工具三:UIImage对象的Memory缓存
第三个工具也是为了提高数据流畅度而生的,就是图片的对象缓存。我们将已经初始化过的图片进行缓存,等下次再使用该图片时直接从缓存中读取,从而节省了在主线程中创建对象和销毁对象的时间,从而可以提高UI的流畅度。当然此处我实现的图片的内存缓存比较简单,也就是在本Demo中适用。不过原理还是OK的,全面的MemoryCache请参考YYKit中的YYMemoryCache。其中用到了双向链表以及CFMutableDictionaryRef来实现的MemoryCache,其源码并不是很难理解,有兴趣的小伙伴可以进行阅读呢。
本篇博客所实现的Memory缓存就比较简单了,就使用了一个字典,字典的Key是图片的名称,字典的Value是已经创建的字典的对象。代码比较简单,下方是核心代码。大体原理就是在获取时,如果缓存字典中没有相应的对象就进行创建并加入缓存,然后返回该对象。如果缓存中已经有该对象,则直接返回。核心代码如下。

三、Autolayout + AutomaticDimension
上一部分已经为Demo的开发做好了准备,接下来就开始进入今天真正的主题。首先我们来介绍Autolayout + AutomaticDimension的实现方式。使用这种方式来是Cell高度的自适应比较简单,但不高效。下方是我们所使用的Cell的布局,当然是使用AutoLayout来实现的。因为下方test的内容的长度是不定的,所以我们为test所对应的TextView添加的约束为(top, left right, bottom)。这样test的高度就可以随着Cell的高度而改变了。
约束添加完毕后,我们的工作基本上就已经完成了,接下来需要进行简单的配置,我们的Cell高度自适应就OK了。下方就是我们添加完约束后要做的事情,需要给我们的tableView设置一个预估值(estimatedRowHeight), 然后在TableViewDelegate的heightForRowAtIndexPath方法中返回UITableViewAutomaticDimension该属性即可。这样Cell就可以根据可变的文字高度来自适应了。当然该方法在iOS8以上的系统上才可以使用。

经过上述这两步,我们的Cell就可以进行自适应了,下方是该解决方案所对应的运行效果。可以看出来卡顿还是比较明显的,掉帧比较严重,在Cell高度自适应时最好不要采用此方法。也就是说这种方法,并不适用在我们Cell列表中来预估每个Cell的高度。那这种方式是不是就没用了呢?当然不是,填写内容的Cell上是可以使用这种方法进行预估的,也就是说,当根据用户输入的内容来实时改变Cell的高度,是可以使用该方法的。

四、Autolayout +CountHeight
接下来我们对上述的效果进行优化,不使用TableView的预估值了,而是直接使用我们在子线程中计算的文本高度。当然依然是使用AutoLayout的方式,将上述返回高度的方法heightForRowAtIndexPath中的内容进行替换,直接返回当前Model中Cell的高度,如下所示:

经过上面这么一修改,我们就可以将之前Cell高度计算的内容移到子线程中了,上述的卡顿问题会得到些微的解决。下方是该方式的运行效果,可以看出来比上述的实现方式稍微好一些,不过还是有些掉帧,掉帧也是比较严重的。

五、FrameLayout + CountHeight
上述结果仍然不理想,我们接着优化。我们不使用AutoLayout布局,我们直接使用Frame来布局,这样就减少了由AutoLayout转换到FrameLayout的时间。本部分我们就使用纯代码的方式,以Autolayout进行布局。在给Cell配置数据的时候我们根据Model中计算的高度来修改可变文字内容的高度,如下所示:

下方是使用这种方式最终的运行效果,从该效果中可以看出,效果还是蛮OK的。虽然有些掉帧,但是还是非常流畅的,这种流畅度是可以接受的。如果你不想使用第三方库的话,这种方式还是一个比较好的解决方案的。

六. YYKit + CountHeight
接下来我们进一步进行优化,引入第三方UI组件YYKit。将Cell上的组件替换成YYKit所提供的组件。然后使用Frame进行布局,当然也是在子线程中对Cell的高度进行计算了。当然此处只是对YYKit简单的使用,应该还有更好的优化方式,只是此处没有给出,欢迎相互交流。

看来将进行系统的基础控件换成了YYKit中的控件,下方是此解决方案的运行效果。单从效果上来看,还是比较流畅的,但是为达到完全不掉帧的效果。不过整体看来还是比较流畅的。

七、AsyncDisplayKit + CountHeight
接下来我们要用Facebook提供的第三方库来进行基础组件的替换,将我们使用到的组件替换成AsyncDisplayKit相应的Note,如下所示。这些Note是对系统组件的重组,对组件的显示进行了优化,让其渲染更为流畅。

下方就是使用AsyncDisplayKit重构后运行的效果。从下方的效果上来看,几乎不掉帧,那个流畅呢。如果你对UI流畅度要求比较高的话,那么AsyncDisplayKit是一个比较好的选择。不过会严重依赖AsyncDisplayKit,如果AsyncDisplayKit停止维护了,后期对AsyncDisplayKit进行替换的话,工作量还是比较大的。因为这种布局框架不像网络框架,我们可以对网络框架的调用进行提取,网络层统一对外接口,很方便切换到其他网络请求库。但是像AsyncDisplayKit这种框架会散布于UI层的各个角落,封装提取不易,更不用说轻而易举的替换了。所以像这种页面的实现,个人还是偏向于Framelayout + CountHeight的方式来实现。

八、Demo中用到的设计模式
经过上面这7步,我们Demo的功能以及效果已经介绍完毕,不同实现方式优缺点一目了然。该部分也是本篇博客最后一部分,我们就来聊一下本篇博客中所使用的设计模式。我们可以看出上述几个列表的页面是完全一样的,只是Cell的实现方式不同。所以我们可以将TableView提取成基类,TableView中所使用的Cell类型由子类来确定。说的官方一些,这就是策略模式。具体的Cell使用策略由具体的TableView来定,而父类TableView值负责根据子类提供的策略来进行Cell的初始化。
我们就以AsyncDisplayKitTableViewController和FrameCountTableViewController这两个类为例,下方就是这两个TableViewController的相关代码。下方这两个类的基类都是SuperTableViewController。大部分工作都在基类中去实现了,而子类中只提供了使用Cell的策略。这就是策略模式的好处,便于扩充,如果有类似的页面,子类只提供Cell的类型即可。下方这两个类中的getReuseIdentifier方法就是为父类提供策略的方法。


当然不知上述类有父类,具体Cell的基类也得有父类,因为在TableViewController中声明Cell时用的是Cell的父类,如下所示。此处用到了面向对象的多态性,并且也用到了面向接口原则。此处SuperTableViewCell虽然是一个基类,但是它也担负着定义子类接口的责任。好处就不多说了吧。 
关于设计模式相关的内容,请查看之前发布的关于设计模式的系列博客《设计模式系列》,重构的内容的话请查看之前发布重构系列的博客《重构系列》。当然这两个系列的博客全是使用Swift语言实现的Demo,不过思想都是相同的。好了今天博客篇幅也挺长的,就先到这儿吧。
github分享链接:https://github.com/lizelu/DisplayTestDemo
作者:青玉伏案
出处:http://www.cnblogs.com/ludashi/
本文版权归作者和共博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出。以免更多的人被误导。
转:iOS开发之多种Cell高度自适应实现方案的UI流畅度分析的更多相关文章
- iOS开发之多种Cell高度自适应实现方案的UI流畅度分析
本篇博客的主题是关于UI操作流畅度优化的一篇博客,我们以TableView中填充多个根据内容自适应高度的Cell来作为本篇博客的使用场景.当然Cell高度的自适应网上的解决方案是铺天盖地呢,今天我们的 ...
- iOS 开发中单元格cell高度自适应
高度自适应分下面两种情况 1.用代码自定义的cell 用代码自定义的cell,cell高度自定义需要我们手动的去计算每个cell的字符串高度.然后返回对应的高度即可. 2.用XIB 或者 StoreB ...
- iOS - UITextView放在自定义cell里面-自适应高度
textView放在自定义cell里面-自适应高度 1,textView有个属性 scrollEnabled 要设置为NO; 2,设置tableview的时候 添加这两行代码: self.tabl ...
- iOS8 tableView的Cell高度自适应开发
1.在- (void)viewDidLoad中设置: //估计高度为81 self.tableView.estimatedRowHeight = 81.0f; //自适应高度 self.tableVi ...
- iOS开发-UITableView自定义Cell
UITableView在iOS中开发的重要地位是毋庸置疑的,基本上应用中用到的比例是一半左右,而且大部分情况都是需要自定义单元格的,这样用户看到的App才能更有美感.之前写过UITableView的基 ...
- iOS开发 UITableView之cell
1.cell简介 UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowAtIndexPath:方法来初始化每一行 U ...
- iOS 字符串的宽度和高度自适应
//获取字符串的宽度 -(float)widthForString:(NSString *)value fontSize:(float)fontSize andHeight:(float)height ...
- 李洪强iOS开发之自定义cell的使用
第一步: 创建自定义cell类,继承自UItableVIewcell 第二步: 在sb中布局自己需要的视图控件并且将此cell与我刚刚创建的cell类进行关联.并且连线 第三步: 创建modle类, ...
- iOS开发之监听键盘高度的变化
最近做的项目中,有一个类似微博中的评论转发功能,屏幕底端有一个输入框用textView来做,当textView成为第一响应者的时候它的Y值随着键盘高度的改变而改变,保证textView紧贴着键盘,但又 ...
随机推荐
- Winwos Server 2012发布ASP.NET MVC5 项目
一.本文实验环境: Windows Server 2012 R2 Visual Studio 2015 项目为:ASP.NET MVC 5.0,使用的是SQL SERVER 2008 R2数据库 二. ...
- spring boot 中 Mybatis plus 多数据源的配置方法
最近在学习spring boot,发现在jar包依赖方面做很少的工作量就可以了,对于数据库操作,我用的比较多的是mybatis plus,在中央仓库已经有mybatis-plus的插件了,对于单数据源 ...
- 将百度的ECharts整合到阿里的Weex中。
由于公司的业务,之前PC版产品中,大量的使用了百度的ECharts库.所以现在要做移动端,在大概熟悉了Weex基本语法和搭建环境后,就着手研究如何将这两个好东西糅合起来. 首先,按照Weex官方教程, ...
- php中foreach中使用&的办法
刚开始在使用foreach时候一直不理解为什么要使用& 后来发现在给一个数组里面添加数据时候很好用 <?phpheader("Content-Type:text/html;ch ...
- warning: C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失
------问题-------------------- Qt项目使用 VC++ 编译器出现此错误. warning: C4819: 该文件包含不能在当前代码页(936)中表示的字符.请将该文件保存为 ...
- ##3.Keystone 验证服务--openstack
##3.Keystone 验证服务 openstack pike 安装 目录汇总 http://www.cnblogs.com/elvi/p/7613861.html #SQL上创建数据库并授权 #K ...
- yii2.0中添加二维数组,多条数据。
/** * @inheritdoc 批量添加 * @params $add array 添加数据 */public function add_all($add){ $connection = \Yii ...
- Linux积累 命令之cat和wc
cat主要有三大功能: 1.一次显示整个文件. $ cat filename 2.从键盘创建一个文件. $ cat > filename 只能创建新文件,不能编辑已有文件. 3.将几个文 ...
- poj 1318Word Amalgamation
题目链接:http://poj.org/problem?id=1318 /*题意:在字母乱序的单词里面找到字母相同的字典里面的单词*/ /*此题的主要思路是要将字符排序,然后找对应,如果相同,那么就将 ...
- 使用storyboard设置button边框属性(颜色,宽度,圆角)
通常使用Category时.仅仅能加入方法,不可加入属性.可是在使用Storyboard时我们可能会使用到keyPath,这里设置的key都须要是所设置视图的属性值.而且类型有所限制. 比如:我如今有 ...