原文链接 :https://github.com/lbj96347/DynamicHeights
DynamicHeights是一个动态表格元素高度(动态TableViewCell)的例子。实际应用场景在iOS开发中会经常遇到不规律的TableViewCell,往往需要根据内容的多少而动态调整这些Cell的大小。在http://www.cimgf.com/2009/09/23/uitableviewcell-dynamic-height/ 中,有详细的解说开发的流程以及这么处理的原因。里面的大部分代码也来源于这位作者。
随着iOS 7的升级,这段09年编写的代码中已经有部分的代码被认为是不建议使用的,因此我对该项目部分代码进行了修改。便于日后方便使用或者学习参考之用。
P.S : 以下内容代码部分,注释内容为我改进的代码段,减少issues警告。
大概你第一眼看来,动态调整高度是一件不容易的事情,而且打算解决它的第一个想法往往是不正确的。在这篇文章中我将展示如何使图表单元格的高度能根据里面文本内容来动态改变,同时又不必子类化UITableViewCell。你当然可以通过子类化它来实现,但是这样做会使得代码复杂因为设置高度是在图表本身的实例上而不是对单元格操作。下面你将会看到这其实是一件轻而易举的事情。对于图表来说能够动态调整高度是件很有意义的事情,我首先想到的需要这个功能的是当显示一列长度会变化的文本列表时,如果文本内容较少,它或许能够适合正常的单元格label,但是如果文本变长,就不得不重新设置单元格大小以便于显示全部的文本内容。我总结了重新设置单元格大小的主要步骤如下:
下面我要详细介绍这些步骤,首先看一下程序输出截图:
在普通的图表中,你可以简单地用下面的方法设置单元格内label的文本内容:
[[cell textLabel] setText:@"Text for the current cell here."];
也许你认为这样做就可以完全控制UILabel了,但是我发现我的任何要改变UILabel框大小的尝试都失败了,因此这并不是实现动态调整大小的一个好的候选方案。
我们需要设计一个UILabel然后把它添加到单元格的内容视图中。要实现它需要调用-cellForRowAtIndexPath,大致内容如下所示:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
UILabel *label = nil;
cell = [tv dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"] autorelease];
label = [[UILabel alloc] initWithFrame:CGRectZero];
//[label setLineBreakMode:UILineBreakModeWordWrap];
[label setLineBreakMode:NSLineBreakByWordWrapping];
//[label setMinimumFontSize:FONT_SIZE];
[label setMinimumScaleFactor:FONT_SIZE];
[label setNumberOfLines:0];
[label setFont:[UIFont systemFontOfSize:FONT_SIZE]];
[label setTag:1];
[[cell contentView] addSubview:label];
}
}
这并不是完整的代码因为我们仅仅在创建单元格的时候初始化它的label,这段代码对应调用-dequeueReusableCellWithIdentifier之后的判断模块if(cell == nil)。 在这里我想强调两点:第一个,我们可以注意到label有一个标签与其对应,因为调用了-setTag:1。当cell不等于nil时这个标签可以用到。第二点,我们通过调用[[cell contentView] addSubview:label]来将label添加到单元格的内容视图中,这个只是在label初始化的时候用到。每调用这个函数都会添加label到子视图序列中。下面我们会将这段代码补充完整,但之前先让我们看一下如何设置cell的高度。
计算cell的高度
在一个复杂的cell中,计算高度可能比较困难,但是你只需要关心那些高度会变化的部件就可以了。在我的例子中,唯一需要处理的就是添加到单元格中的label。我们根据文本的大小来计算cell 的高度,而文本的大小取决于文本的长度和文本字体。NSString类提供了函数-sizeWithFont来方便我们获取cell 的大小。下面的代码介绍了函数-heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
NSString *text = [items objectAtIndex:[indexPath row]];
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
//CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
NSAttributedString *attributedText = [[NSAttributedString alloc]initWithString:text attributes:@{
NSFontAttributeName:[UIFont systemFontOfSize:FONT_SIZE]
}];
CGRect rect = [attributedText boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
CGFloat height = MAX(size.height, 44.0f);
return height + (CELL_CONTENT_MARGIN * 2);
}
你会注意到我们用到了几个常量来计算cell 的大小,它们的定义如下所示:
#define FONT_SIZE 14.0f
#define CELL_CONTENT_WIDTH 320.0f
#define CELL_CONTENT_MARGIN 10.0f
常量CELL_CONTENT_WIDTH是整个cell的宽度。CELL_CONTENT_MARGIN是我们定义的页边空白,FONT_SIZE是我们采用文本的字体大小。
首先我们要创建一个内容宽度的约束条件。CGSizeMake的第一个参量是总共的内容宽度减去两个页边空白。因为左边和右边各有一个页边空白。第二个参数是我们提供的最大数值。这个约束条件在后面的函数-sizeWithFont中将会用到。在-sizeWithFont中我们设置为UILineBreakModeWordWrap来获取在允许自动换行的情况和上面提到的约束条件下正确的大小。最后我们使用MAX宏设置cell的高度,并且保证cell 的高度不会小于44个像素,因为它返回size.height和44两个数中的最大值。最后,我们将上下的页边空白考虑进去得到最后的结果。
为了使得读者形象化的了解页边空白,下面一个截图可以看出有一个边界环绕着label。调用[[label layer] setBorderWidth:2.0f]可以显示该边界从而方便我们看到页边空白。
计算并设置UILabel框大小
在前面我们用来计算高度的方法也是我们用来设置UILabel框大小的方法。下面将-cellForRowAtIndexPath代码补充完整:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
UILabel *label = nil;
cell = [tv dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"] autorelease];
label = [[UILabel alloc] initWithFrame:CGRectZero];
//[label setLineBreakMode:UILineBreakModeWordWrap];
[label setLineBreakMode:NSLineBreakByWordWrapping];
//[label setMinimumFontSize:FONT_SIZE];
[label setMinimumScaleFactor:FONT_SIZE];
[label setNumberOfLines:0];
[label setFont:[UIFont systemFontOfSize:FONT_SIZE]];
[label setTag:1];
[[label layer] setBorderWidth:2.0f];
[[cell contentView] addSubview:label];
}
NSString *text = [items objectAtIndex:[indexPath row]];
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
//CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
NSAttributedString *attributedText = [[NSAttributedString alloc]initWithString:text attributes:@{
NSFontAttributeName:[UIFont systemFontOfSize:FONT_SIZE]
}];
CGRect rect = [attributedText boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
if (!label)
label = (UILabel*)[cell viewWithTag:1];
[label setText:text];
[label setFrame:CGRectMake(CELL_CONTENT_MARGIN, CELL_CONTENT_MARGIN, CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), MAX(size.height, 44.0f))];
return cell;
}
要注意if(cell == nil)模块是初始化代码,只在cell创建的时候运行一次。该模块外部代码每次都会执行只要在每次数据更新或者窗口拖拽之后调用了-cellForRowAtIndexPath。
也就是说,每次都需要设置label中文本内容以及设置label外框大小。注意如果label处于未初始化状态,我们需要通过调用[cell viewWithTag:1]来获取UILabel的句柄。这段代码跟前面计算高度的代码基本相同。
总结
动态计算单元格cell的高度真的并不困难。如果你有一个很复杂的cell,你只需要根据内容宽度和特定文本字体的大小来确定cell的高度。如果你不清楚你的外框显示在什么地方,只需要通过调用[[view layer] setBorderWidth:2.0f]来使外框显示即可。这会有助于你了解绘图过程以及更快地在更深的层次理解绘图显示的问题。
作者:Matt Long
翻译文章:友盟翻译组 stefaliu
原文链接:http://www.cimgf.com/2009/09/23/uitableviewcell-dynamic-height/
翻译原文地址:(暂时未找到,抱歉了)