动态计算UITableViewCell高度详解

本文将介绍四种状况下UITableViewCell的计算方式,分别是:app

1. Auto Layout with UILabel in UITableViewCell布局

2. Auto Layout with UITextView in UITableViewCell字体

3. Manual Layout with UILabel in UITableViewCellatom

4. Manual Layout with UITextView in UITableViewCellspa

5. 随UITextView高度动态改变Cell高度prototype

 

首先建立一个Single Page的工程,我命名为CellHeightDemocode

 

1. Auto Layout with UILabel in UITableViewCellorm

建立一个空的xib,命名为C1.xib, 而后拖入一个UITableViewCell控件。接着建立一个UITableViewCell的子类,命名为C1类。而后在C1.xib中,将与C1类进行关联。别给我说你不会关联,若是不会那看下图你就明白了。 教程

 

只须要在Class那里写入关联的类名C1便可。图片

 

还有因为UITableViewCell须要重用功能,因此咱们还须要设置一个重用标识 

 

在Identifier那里写入重用标识C1,固然你也能够用任意字符。不事后面代码里须要这个字符。

 

接着咱们来布局。用到了auto layout, 在此我不想介绍auto layout, 之后有时间再专门介绍,下图就是我布局 

 

这儿有两点须要说明:1. UILabel的属性Lines这儿设为了0表示显示多行。2. Auto Layout必定要创建完完整。

 

接着咱们在UITableView中来使用咱们自定义的UITableViewCell C1。

 

首先咱们建立一个UITableViewController的子类T1ViewController, 接着在Main.storyboard中拖入一个UITableViewController,并关联T1ViewController。

 

一切都准备好了,那咱们如今来写点代码,给UITableView加点料。

 

咱们想要咱们的UITableView使用C1.xib中自定义的Cell,那么咱们须要向UITableView进行注册。

UINib *cellNib = [UINib nibWithNibName:@"C1" bundle:nil]; [self.tableView registerNib:cellNib forCellReuseIdentifier:@"C1"];

 

这样就进行注册了,接着咱们还须要每行显示的数据,为了简单一点,我就声明了一个NSArray变量来存放数据。

self.tableData = @[@"1\n2\n3\n4\n5\n6", @"123456789012345678901234567890", @"1\n2", @"1\n2\n3", @"1"];

 

如今实现UITableViewDataSource的protocol:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {     // Return the number of rows in the section.     return self.tableData.count; }   - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {     C1 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C1"];     cell.t.text = [self.tableData objectAtIndex:indexPath.row];     return cell; }

 

从self.tableData中的数据咱们能够看到,每个Cell显示的数据高度是不同的,那么咱们须要动态计算Cell的高度。因为是auto layout,因此咱们须要用到一个新的API systemLayoutSizeFittingSize:来计算UITableViewCell所占空间高度。Cell的高度是在- (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath这个UITableViewDelegate的方法里面传给UITableView的。

 

这里有一个须要特别注意的问题,也是效率问题。UITableView是一次性计算完全部Cell的高度,若是有1W个Cell,那么- (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath就会触发1W次,而后才显示内容。不过在iOS7之后,提供了一个新方法能够避免这1W次调用,它就是- (CGFloat)tableView:(UITableView )tableView estimatedHeightForRowAtIndexPath:(NSIndexPath )indexPath。要求返回一个Cell的估计值,实现了这个方法,那只有显示的Cell才会触发计算高度的protocol. 因为systemLayoutSizeFittingSize须要cell的一个实例才能计算,因此这儿用一个成员变量存一个Cell的实列,这样就不须要每次计算Cell高度的时候去动态生成一个Cell实例,这样即方便也高效也少用内存,可谓一举三得。

 

咱们声明一个存计算Cell高度的实例变量:

@property (nonatomic, strong) UITableViewCell *prototypeCell;

 

而后初始化它:

self.prototypeCell  = [self.tableView dequeueReusableCellWithIdentifier:@"C1"];

 

下面是计算Cell高度的实现:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {     C1 *cell = (C1 *)self.prototypeCell;     cell.t.text = [self.tableData objectAtIndex:indexPath.row];     CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];     NSLog(@"h=%f", size.height + 1);     return 1  + size.height; }

 

看了代码,可能你有点疑问,为什么这儿要加1呢?笔者告诉你,若是不加1,结果就是错误的,Cell中UILabel将显示不正确。缘由就是由于这行代码CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];因为是在cell.contentView上调用这个方法,那么返回的值将是contentView的高度,UITableViewCell的高度要比它的contentView要高1,也就是它的分隔线的高度。若是你不相信,那请看C1.xib的属性,比较下面两张图。 

 

 

发现没Cell的高度是127, 面contentView的高度是126, 这下明白了吧。

 

为了让读者看清楚,我将Cell中UILabel的背景色充为了light gray.下面是运行效果: 

 

2. Auto Layout with UITextView in UITableViewCell

本小段教程将介绍UITextView在cell中高度须要注意的地方。

 

创始UITableViewController的了类T2ViewController,在Main.storyboard中拖入UITableViewController,并关联他们。接着代码中注册C2.xib到UITableView.

 

下面计是计算高度的代码:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {     C2 *cell = (C2 *)self.prototypeCell;     cell.t.text = [self.tableData objectAtIndex:indexPath.row];     CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];     CGSize textViewSize = [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)];     CGFloat h = size.height + textViewSize.height;     h = h > 89 ? h : 89;  //89是图片显示的最低高度, 见xib     NSLog(@"h=%f", h);     return 1 + h; }

 

在这儿咱们是经过sizeThatFits:计算的UITextView的高度(这是计算UITextView内容所有显示时的方法,在第四小段中咱们还会用到它),而后加上systemLayoutSizeFittingSize:返回的高度。为何要这样呢? 由于UITextView内容的高度不会影响systemLayoutSizeFittingSize计算。这句话什么意思呢?我真不知道如何用言语表达了。仍是先上一张图吧:

  

此图中距顶的约束是10, 距底的约束8, 距左边约束是87,距右边的约束是13, 那么systemLayoutSizeFittingSize:返回的CGSize为height等于19, size等于100. 它UITextView的frame是不影响systemLayoutSizeFittingSize:的计算。不知道这样说你们明白没。

因此,咱们须要加上textViewSize.height. 

 

下面是运行效果: 

 

3. Manual Layout with UILabel in UITableViewCell

本小段教程将介绍UILabel在Manual layout cell中计算高度, 原理是根据字体与字符串长度来计算长度与宽度。 按照前面介绍的,咱们须要建立C3.xib, C3类, T3ViewController类,Main.storyboard中拖入UITableViewController,并分别创建关联。 为了简单,C3.xib中我就不加padding之类的了,如图

 

记得关闭C3.xib的auto layout 

 

直接上代码了:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {     C3 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C3"];     cell.t.text = [self.tableData objectAtIndex:indexPath.row];     [cell.t sizeToFit];     return cell; }   - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {     C3 *cell = (C3 *)self.prototypeCell;     NSString *str = [self.tableData objectAtIndex:indexPath.row];     cell.t.text = str;     CGSize s = [str calculateSize:CGSizeMake(cell.t.frame.size.width, FLT_MAX) font:cell.t.font];     CGFloat defaultHeight = cell.contentView.frame.size.height;     CGFloat height = s.height > defaultHeight ? s.height : defaultHeight;     NSLog(@"h=%f", height);     return 1  + height; }

 

这儿用到了一个NSString的Cagetory方法:

- (CGSize)calculateSize:(CGSize)size font:(UIFont *)font {     CGSize expectedLabelSize = CGSizeZero;          if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {         NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];         paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;         NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle.copy};                  expectedLabelSize = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;     }     else {         expectedLabelSize = [self sizeWithFont:font                                        constrainedToSize:size                                            lineBreakMode:NSLineBreakByWordWrapping];     }       return CGSizeMake(ceil(expectedLabelSize.width), ceil(expectedLabelSize.height)); }

 

原理上面我已说了,这儿没有什么好说明的,代码一目了然。

 

运行效果如图: 

 

4. Manual Layout with UITextView in UITableViewCell

本小段教程将介绍UITextView在Manual layout cell中计算高度, 原理是与第二小节里的相同,用sizeThatFits:的方法计算UITextView的长度与高度。而后加上padding就是Cell的高度。 按照前面介绍的,咱们须要建立C4.xib, C4类, T4ViewController类,Main.storyboard中拖入UITableViewController,并分别创建关联。 为了简单,C4.xib中我就不加padding之类的了,如图

 

记得关闭C4.xib的auto layout 

 

也直接上代码了,直观明了:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {     C4 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C4"];     cell.t.text = [self.tableData objectAtIndex:indexPath.row];     [cell.t sizeToFit];     return cell; }   - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {     C4 *cell = (C4 *)self.prototypeCell;     NSString *str = [self.tableData objectAtIndex:indexPath.row];     cell.t.text = str;     CGSize s =  [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)];     CGFloat defaultHeight = cell.contentView.frame.size.height;     CGFloat height = s.height > defaultHeight ? s.height : defaultHeight;     return 1  + height; }

 

运行效果: 

 

5. 随UITextView高度动态改变Cell高度

本小节要介绍的一个功能是,UITextView中UITableViewCell中,当输入UITextView中的字变多/变少时,高度变化,Cell高度与随之变化的功能。

按照前面介绍的,咱们须要建立C5.xib, C5类, T5ViewController类,Main.storyboard中拖入UITableViewController,并分别创建关联。 为了简单,C5.xib中我就不加padding之类的了,如图

 

记得开启C5.xib的auto layout 

 

先看代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {     C5 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C5"];     cell.t.text = @"123";     cell.t.delegate = self;     return cell; }  #pragma mark - UITableViewDelegate - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {     C5 *cell = (C5 *)self.prototypeCell;     cell.t.text = self.updatedStr;     CGSize s =  [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)];     CGFloat defaultHeight = cell.contentView.frame.size.height;     CGFloat height = s.height > defaultHeight ? s.height : defaultHeight;     return 1  + height; }  #pragma mark - UITextViewDelegate - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {     if ([text isEqualToString:@"\n"]) {         NSLog(@"h=%f", textView.contentSize.height);     }     return YES; }  - (void)textViewDidChange:(UITextView *)textView {     self.updatedStr = textView.text;     [self.tableView beginUpdates];     [self.tableView endUpdates]; }

 

原理就是UITextView内容改变的时候,计算自身高度,而后通知UITableView更新,这样就会触发UITableViewCell高度从新计算,以达到目的。 

相关文章
相关标签/搜索