配色: 字号:
iOS App开发中使cell高度自适应的黑魔法详解
2016-12-05 | 阅:  转:  |  分享 
  
iOSApp开发中使cell高度自适应的黑魔法详解

这篇文章主要介绍了iOSApp开发中使cell高度自适应的黑魔法详解,作者利用iOS8以后的新特性讲解了TableView、CollectionView中的cell高度自适应以及UITextView输入内容实时更新cell高度的方法,需要的朋友可以参考下

在使用tableview的时侯经常会遇到这样的需求:tableview的cell中的内容是动态的,导致在开发的时候不知道一个cell的高度具体是多少,所以需要提供一个计算cell高度的算法,在每次加载到这个cell的时候计算出cell真正的高度。

在iOS8之前

没有使用Autolayout的情况下,需要实现tableviewdelegate的tableView(tableView:UITableView,heightForRowAtIndexPathindexPath:NSIndexPath)->CGFloat方法,在这个方法中计算并返回cell的高度。比如,我有一个可以显示任意行数的纯文本cell,计算cell的代码可以是这样:

复制代码代码如下:

overridefunctableView(tableView:UITableView,heightForRowAtIndexPathindexPath:NSIndexPath)->CGFloat{letcontent=self.datas[indexPath.row]asString

letpadding:CGFloat=20letwidth=tableView.frame.size.width-padding2;

letsize=CGSizeMake(width,CGFloat.max)letattributes=[NSFontAttributeName:UIFont(name:"Helvetica",size:14)!]letframe=content.boundingRectWithSize(size,options:NSStringDrawingOptions.UsesLineFragmentOrigin,attributes:attributes,context:nil)returnframe.size.height+1;}

上面的代码是一个最简单的例子,这个例子看起来好像没有什么问题。但是通过查看这个delegate方法的文档后,可以知道,在每次reloadtableview的时候,程序会先计算出每一个cell的高度,等所有高度计算完毕,确定了tableview的总的高度后,才开始渲染视图并显示在屏幕上。这意味着在显示tableview之前需要执行一堆的计算,并且这是在主线程中进行的,如果计算量太大程序就很有可能出现卡顿感。比如:tableview的数据有上千条,或者计算高度的代码中还要先获取图片再根据图片计算高度,这些操作都是非常慢的。

如果在cell中使用了autolayout,在计算cell高度时会更麻烦。有兴趣的可以看这里有篇关于如何在autolayout下动态计算高度的文章。

为什么不能等滚动到某个cell的时候,再调用计算这个cell高度的delegate呢?原因是tableview需要获得它的内容的总高度,用这个高度去确定滚动条的大小等。直到iOS7UITableViewDelegate中添加了新的API

tableView(tableView:UITableView,estimatedHeightForRowAtIndexPathindexPath:NSIndexPath)->CGFloat这个方法用于返回一个cell的预估高度,如果在程序中实现了这个方法,tableview首次加载的时候就不会调用heightForRowAtIndexPath方法,而是用estimatedHeightForRowAtIndexPath返回的预估高度计算tableview的总高度,然后tableview就可以显示出来了,等到cell可见的时候,再去调用heightForRowAtIndexPath获取cell的正确高度。

通过使用estimatedHeightForRowAtIndexPath这个Delegate方法,解决了首次加载tableview出现的性能问题。但还有一个麻烦的问题,就是在cell没有被加载的时候计算cell的高度,上面给出的代码中,仅仅是计算一个NSString的高度,就需要不少代码了。这种计算实际上是必须的,然而在iOS8开始,你可能可以不用再写这些烦人的计算代码了!

iOS8的魔法

在iOS8中,selfsizecell提供了这样一种机制:cell如果有一个确定的宽度/高度,autolayout会自动根据cell中的内容计算出对应的高度/宽度。

TableView中的cell自适应

要让tableview的cell自适应内容,有几个要点:

设置的AutoLayout约束必须让cell的contentView知道如何自动延展。关键点是contentView的4个边都要设置连接到内容的约束,并且内容是会动态改变尺寸的。UITableView的rowHeight的值要设置为UITableViewAutomaticDimension和iOS7一样,可以实现estimatedHeightForRowAtIndexPath方法提升tableview的第一次加载速度。任何时候cell的intrinsicContentSize改变了(比如tableview的宽度变了),都必须重新加载tableview以更新cell。例子

在Xcode中新建一个项目,在storyboard中创建一个UITableViewController的IB,创建一个如下样子的cell:



这个cell中有3个元素,其中imageView的autoLayout约束为:

imageView左边离contentView左边0

imageView上边离contentView上边0

imageView的width和height为80

imageView下边离contentView下边大于等于0(为了防止内容太少,导致cell高度小于图片高度)

titleLabel的autoLayout约束为:

titleLabel左边离imageView右边8

titleLabel上边和imageView上边在同一只线上

titleLabel右边离contentView右边0

titleLabel下边离description上边8

titleLabel的高度小于等于22,优先级为250

descriptionLabel的约束为:

descriptionLabel左边和titleLabel左边在同一直线上

descriptionLabel上边里titleLabel8

descriptionLabel下边里contentView下边0

descriptionLabel右边离contentView右边0

然后在这个IB对应的UITableViewController中加载一些数据进去,显示效果如图:



实现这个效果,我除了设置了autoLayout,还设置了tableView的rowHeight=UITableViewAutomaticDimension,然后就是这样了。一点计算cell高度的代码都没有!!我连heightForRowAtIndexPath都不用实现,真的是….爽出味啊!所以如果已经在开发iOS8Only的应用了一定要用autolayout,把烦人的计算交给autolayout去吧。

CollectionView中的cell自适应

在collectionview中也能让cell自适应内容大小,如果UICollectionView的layout是一个UICollectionViewFlowLayout,只需要将layout.itemSize=...改成layout.estimatedItemSize=...。只要设置了layout的estimatedItemSize,collectionview就会根据cell里面的autolayout约束去确定cell的大小。

原理:

collectionview根据layout的estimatedItemSize算出估计的contentSize,有了contentSizecollectionview就开始显示

collectionview在显示的过程中,即将被显示的cell根据autolayout的约束算出自适应内容的size

layout从collectionview里获取更新过的sizeattribute

layout返回最终的sizeattribute给collectionview

collection使用这个最终的sizeattribute展示cell

UITextView输入内容实时更新cell的高度在一个动态数据的tableview中,cell根据textview内容的输入实时改变cell和tableview的高度。自动计算cell高度的功能使用iOS8才支持的自适应cell,先上图,我们最终要实现的效果是这样的:



实现上面效果的基本原理是:

在cell中设置好textview的autolayout,让cell可以根据内容自适应大小

textview中输入内容,根据内容更新textView的高度

调用tableView的beginUpdates和endUpdates,重新计算cell的高度

将textview更新后的数据保存,以免tableview滚动超过一屏再滚回来textview中的数据又不刷新成原来的数据了。

功能具体实现方法:

新建一个项目,拉出TableViewController,在cell上添加一个UITextView。

首先设置textview的autolayout,比较关键的constraint是要设置textView的高度大于等于一个值。如图:



然后,设置UITextView的scrollEnable为NO。这一点很关键,如果不设置为NO,UITextView在内容超出frame后,重新设置textview的高度会失效,并出现滚动条。



根据刚才在storyboard中创建的cell,新建一个UITableViewCell类。

复制代码代码如下:

#import

@interfaceTextViewCell:UITableViewCell

@property(weak,nonatomic)IBOutletUITextViewtextView;

@end

创建TableViewController并初始化一些数据

复制代码代码如下:

#import"TableViewController.h"#import"TextViewCell.h"

@interfaceTableViewController()

@property(nonatomic,strong)NSArraydata;

@end



复制代码代码如下:

@implemenwww.visa158.comtationTableViewController

-(void)viewDidLoad{[superviewDidLoad];

//支持自适应cellself.tableView.estimatedRowHeight=100;self.tableView.rowHeight=UITableViewAutomaticDimension;

self.data=@[@"Cell1",@"Cell2",@"Cell3",@"Cell4",@"Cell5",@"Cell6",@"Cell7",@"Cell8"];}

#pragmamark-Tableviewdatasource

-(NSInteger)tableView:(UITableView)tableViewnumberOfRowsInSection:(NSInteger)section{return[self.datacount];}

-(UITableViewCell)tableView:(UITableView)tableViewcellForRowAtIndexPath:(NSIndexPath)indexPath{TextViewCellcell=[tableViewdequeueReusableCellWithIdentifier:@"TextViewCell"forIndexPath:indexPath];cell.textView.text=self.data[indexPath.row];returncell;}

使用上面的代码项目已经可以运行了,但是textview还不能自动更新大小,下面来实现textview根据内容计算高度

先在storyboard中,将UITextView的delegate设置为cell



在TextViewCell.m中实现-(void)textViewDidChange:(UITextView)textView,每次textview内容改变的时候,就重新计算一次textview的大小,并让tableview更新高度。

复制代码代码如下:

#import"TextViewCell.h"

@implementationTextViewCell

-(void)textViewDidChange:(UITextView)textView{CGRectbounds=textView.bounds;

//计算textview的高度CGSizemaxSize=CGSizeMake(bounds.size.width,CGFLOAT_MAX);CGSizenewSize=[textViewsizeThatFits:maxSize];bounds.size=newSize;

textView.bounds=bounds;

//让tableview重新计算高度UITableViewtableView=[selftableView];[tableViewbeginUpdates];[tableViewendUpdates];}

-(UITableView)tableView{UIViewtableView=self.superview;

while(![tableViewisKindOfClass:[UITableViewclass]]&&tableView){tableView=tableView.www.hunanwang.netsuperview;}

return(UITableView)tableView;}

@end

这样就已经实现了textview改变内容自动更新cell高度的功能,这篇文章没有涉及到计算cell高度的代码,因为计算cell高度的工作全部交给iOS8的autolayout自动计算了,这让我们少写了许多令人痛苦的代码。

最后:为了防止tableview过长,导致滚动后重新加载cell,会让textview中的内容还原的问题,我们应该在更新了textview的内容之后保存数据。(如果是在编辑状态下,还需要考虑取消编辑后的回滚功能。普通数组数据,可以保存一个原始数据的副本,如果用户取消编辑,就设置data为原始数据的副本。如果是NSManagedObject对象可以使用NSUndoManage,不过这些已经超出本篇文章的内容范围了。)

为了在textview更新后能让TableViewController中的data更新,需要为cell添加一个delegate,在textview更新后调用delegate,TableViewController中收到delegate信息后更新data。

修改后的TextViewCell.h

复制代码代码如下:

#import

@protocolTextViewCellDelegate;

@interfaceTextViewCell:UITableViewCell

@property(weak,nonatomic)IBOutletUITextViewtextView;

@property(weak,nonatomic)iddelegate;

@end



复制代码代码如下:

@protocolTextViewCellDelegate

-(void)textViewCell:(TextViewCell)celldidChangeText:(NSString)text;

@end

在TextView.m的-(void)textViewDidChange:(UITextView)textView中添加delegate的调用

复制代码代码如下:

-(void)textViewDidChange:(UITextView)textView{if([self.delegaterespondsToSelector:@selector(textViewCell:didChangeText:)]){[self.delegatetextViewCell:selfdidChangeText:textView.text];}

//计算textview的高度...//让tableview重新计算高度...}

最后在TableViewController.m的最后实现TextViewCellDelegate的方法,更新data

复制代码代码如下:

#pragmamark-TextViewCellDelegate

-(void)textViewCell:(TextViewCell)celldidChangeText:(NSString)text{NSIndexPathindexPath=[self.tableViewindexPathForCell:cell];

NSMutableArraydata=[self.datamutableCopy];data[indexPath.row]=text;self.data=[datacopy];}























献花(0)
+1
(本文系白狐一梦首藏)