这几年APP越来越推崇体验至上,随便写写用户很难买账。一个平滑的列表是非常重要的一点。如果一个App的页面滚动总是卡顿,至少会被当成反面教材来吐槽或掀起“我们的Appbalabala……”,大不了直接卸载。正好最近在优化这方面,所以总结记录一下。如果有什么好的博文推荐,ibireme的iOS保持界面流畅的小技巧这篇文章堪称业界毒瘤,墙裂推荐反复阅读。这篇文章讲解了很多优化点,我总结了两个收益最高的优化点:避免重复计算单元格行高和异步渲染文本。大家可以看看上图的对比分析,数据是在iPhone6上用仪器抓取的。左边是AutoLayout绘制的界面数据分析。通常情况下,要想滚动流畅,fps至少需要稳定在55以上。我们可以发现,在没有cachelineheight和异步渲染的情况下,fps越低越好,可以说是相对卡住,至少肉眼能感觉到,只能满足cacheline高和异步渲染时平滑滚动的要求;右图是直接不使用AutoLayout使用框架绘制界面的数据分析,可以发现即使不使用异步渲染,也勉强能满足平滑滚动的要求。如果开启异步渲染,可以说是相当丝滑了。避免单元格行高重复计算TableView行高计算可以说是老生常谈了,heightForRowAtIndexPath:是一个调用比较频繁的方法,在里面做太多事情难免会卡顿。在iOS8中,我们可以通过设置如下两个属性轻松实现高度适配:self.tableView.estimatedRowHeight=88;self.tableView.rowHeight=UITableViewAutomaticDimension;虽然很方便,但是如果你的页面有性能问题那是必须要的,建议不要这样做。具体可以看sunnyxx优化的UITableViewCell高度计算。对于AutoLayout,提供了单元格行高的缓存库UITableView-FDTemplateLayoutCell,可以帮助我们避免多次计算单元格行高的问题。如果不使用AutoLayout,我们可以在请求获取到数据后,预先计算出页面上各个控件的frame和cellheight,缓存到内存中。使用时直接在heightForRowAtIndexPath:中取出计算出的值即可,大致过程如下:模拟请求数据回调:-(void)viewDidLoad{[superviewDidLoad];[selfbuildTestDataThen:^(NSMutableArray*entities){self.data=@[].mutableCopy;@autoreleasepool{for(FDFeedEntity*entityinentities){FrameModel*frameModel=[FrameModelnew];frameModel.entity=entity;[self.dataaddObject:frameModel];}}[self.tvFeedreloadData];}];}计算框架和单元格行高的简单方法://FrameModel.h@interfaceFrameModel:NSObject@property(assign,nonatomic,readonly)CGRecttitleFrame;@property(assign,nonatomic,readonly)CGFloatcellHeight;@property(strong,nonatomic)FDFeedEntity*entity;@end//FrameModel.m@implementationFrameModel-(void)setEntity:(FDFeedEntity*)entity{if(!entity)return;_entity=entity;CGFloatmaxLayout=([UIScreenmainScreen].bounds.size.width-20.f);CGFloatbottom=4.f;//titleCGFloattitleX=10.f;CGFloattitleY=10.f;CGSizetitleSize=[entity.titleboundingRectWithSize:CGSizeMake(maxLayout,CGFLOAT_MAX)选项:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeadingattributes:@{NSFontAttributeName:Font(16.f)}context:nil].size;_titleFrame=CGRectMake(titleX,titleY,titleSize.width,title);/Size.height=(CGRectGetMaxY(_titleFrame)+bottom);}@end行高值:-(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{FrameFeedCell*cell=[tableViewdequeueReusableCellWithIdentifier:FrameFeedCellModelPathIdentifierforIndexPath];Findex:index*frameModel=self.data[indexPath.row];cell.model=frameModel;returncell;}-(CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath{FrameModel*frameModel=self.data[indexPath.row];returnframeModel.cellHeight;}控制赋值:-(void)setModel:(FrameModel*)model{if(!model)return;_model=model;FDFeedEntity*entity=model.entity;self.titleLabel.frame=model.titleFrame;self.titleLabel.text=entity.title;}优缺点缓存行高method有现成的库,简单方便,虽然UITableView-FDTemplateLayoutCell已经处理的很好了,但是AutoLayout还是会消耗一些性能;手动计算frame的方法需要对所有位置进行计算,比较繁琐,而且在数据量很大的情况下,大量的计算对数据的展示时间会有一定的影响,相应的payoff是更好的性能有些文字是异步渲染的当显示大量文字时,CPU的压力会非常大。解决办法只有一个,那就是自定义文本控件,使用TextKit或者一级CoreText异步绘制文本。虽然这样实现起来很麻烦,但是带来的好处也是非常大的。CoreText对象创建后,可以直接获取文本的宽高等信息,避免多次计算(调整UILabel大小时计算一次,绘制UILabel时重新内部计算一遍));CoreText对象占用内存少,可以缓存起来供以后多次渲染。幸运的是,还有一个现成的库YYText支持文本的异步渲染。下面说说怎么搭配才能最好的满足我们对丝滑的需求:异步渲染Frame的基本思路和计算帧类似,只是将系统的boundingRectWithSize:和sizeWithAttributes:换成了YYText中的方法:配置框架模型://FrameYYModel.h@interfaceFrameYYModel:NSObject@property(assign,nonatomic,readonly)CGRecttitleFrame;@property(strong,nonatomic,readonly)YYTextLayout*titleLayout;@property(assign,nonatomic,readonly)CGFloatcellHeight;@property(strong,nonatomic)FDFeedEntity*entity;@end//FrameYYModel.m@implementationFrameYYModel-(void)setEntity:(FDFeedEntity*)entity{if(!entity)返回;_entity=entity;CGFloatmaxLayout=([UIScreenmainScreen].bounds.size.width-20.f);CGFloatspace=10.f;CGFloatbottom=4.f;//titleNSMutableAttributedString*title=[[NSMutableAttributedStringalloc]initWithString:entity.title];title.yy_font=Font(16.f);title.yy_color=[UIColorblackColor];YYTextContainer*titleContainer=[YYTextContainercontainerWithSize:CGSizeMake(maxLayout,CGFLOAT_MAX)];_titleLayout=[YYTextLayoutlayoutWithContainer:标题eContainertext:title];CGFloattitleX=10.f;CGFloattitleY=10.f;CGSizetitleSize=_titleLayout.textBoundingSize;_titleFrame=(CGRect){titleX,titleY,CGSizeMake(titleSize.width,titleSize.height)};//cellHeight_cellHeight=(CGRectGetMaxY(_titleFrame)+bottom);}@end对比上面的frame,可以发现多了一个YYTextLayout属性,可以预先配置好文字的特性,包括字体,textColor,行数,行间距,innerspacing等。优点是可以提前处理一些逻辑,比如根据界面字段动态配置字体颜色,字体大小等。如果使用AutoLayout,这部分逻辑必然需要写在cellForRowAtIndexPath:方法中UITableViewCell处理:-(instancetype)initWithStyle:(UITableViewCellStyle)stylereuseIdentifier:(NSString*)reuseIdentifier{self=[superinitWithStyle:stylereuseIdentifier:reuseIdentifier];if(!self)returnnil;YYLabel*title=[YYLabelronouslynew];title.displays=Y;//启用异步渲染title.ignoreCommonProperties=YES;//忽略属性title.layer.borderColor=[UIColorbrownColor].CGColor;title.layer.cornerRadius=1.f;title.layer.borderWidth=1.f;[self.contentViewaddSubview:_titleLabel=title];returnsself;}分配:-(void)setModel:(FrameYYModel*)model{if(!model)return;_model=model;self.titleLabel.frame=model.titleFrame;self.titleLabel。textLayout=model.titleLayout;//直接拿YYTextLayout}异步渲染的AutoLayoutYYText很友好,也支持xib,YYText继承自UIView,xib中正常配置约束即可,需要注意的是多行文本中这种情况下,你需要设置换行宽度:CGFloatmaxLayout=[UIScreenmainScreen].bounds.size.width-20.f;self.titleLabel.preferredMaxLayoutWidth=maxLayout;self.subTitleLabel.preferredMaxLayoutWidth=maxLayout;self.contentLabel.preferredMaxLayoutWidth=m布局;优缺点YYText的异步渲染可以大大提高列表的流畅度,真正如丝般顺滑。但是,启用异步后,刷新列表会闪烁。我不知道这是不是一个错误。我最近看到了。作者回来了,相信这个库会越来越好。毕竟是神!如果其他列表中有很多系统设置的圆角页面导致卡顿:label.layer.cornerRadius=5.f;label.clipsToBounds=YES;其实根据我的观察,只要当前屏幕的圆角控件数量不是太多(十几个左右算零点),是不会造成卡顿的。还有,只要不设置clipsToBounds,不管多少都不会被卡住。比如你要圆角的控件背景色是白的,那么它的父控件也是白底色,点击后没有高亮,就不需要clipsToBounds了。综上所述,YYText与UITableView-FDTemplateLayoutCell的结合,可以大大提高列表的流畅度。如果时间紧,可以直接采用AutoLayout+UITableView-FDTemplateLayoutCell+YYText的方式;通过引入这两个库,可以使用系统方法提前计算Frame;如果想要流畅度最大化,需要提前使用Frame+YYText进行计算,可以根据自己的情况选择合适的方案。