其实想要改变UITableView的Cell高度并不难,UITableView带有一个rowHeight属性,使用他就可以改变高度了。但是这样的改变是把所有的Cell的高度都统一改变了。如果存在不同的内容就有不同的Cell高度,那么这时候rowHeight显得力不从心了。不过iOS似乎考虑到了这一点,在UITableView的UITableViewDelegate委托下有一个委托方法可以动态地指定Cell的高度,其声明如下:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
此委托方法就可以根据索引位置indexPath来控制返回的Cell高度。可能有人会认为我实现这个委托就可以实现我上面说的效果了吗?是用到这个委托没错,但是使用这个委托时是会存在一些问题的,下面的内容就是要针对这些问题提出我在实际的开发工作中的处理方法,希望通过共享这些思路能够让童鞋们少走点弯路。
其实主要的问题是heightForRowAtIndexPath这个委托的调用时机是早于cellForRowAtIndexPath这个委托方法的(此方法在UITableViewDataSource中定义)我们都知道cellForRowAtIndexPath是用于返回UITableViewCell的方法。那么问题就在这里了,如果我cell还没返回,那我之前怎么能够在heightForRowAtIndexPath中得到cell的高度呢?也许有人会说:我在返回Cell之后重新刷新指定的Cell就能够得到高度了。但其实这个实践起来是很困难的。而还有一点要注意的是在heightForRowAtIndexPath是不能使用UITableView的cellForRowAtIndexPath:返回Cell对象的。否则会导致无限级递归调用引发的堆栈溢出 。原因是调用此方法会触发heightForRowAtIndexPath委托方法。但是要调用也是可以的。解决方法就是先把delegate为空,等获取到Cell后再重新赋值。如:
tableView.delegate=nil;
UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
tableView.delegate=self;
那么有什么办法可以更加方便实现呢? 我们传统的做法都是在 cellForRowAtIndexPat中排版或者继承UITableViewCell进行排版。而这个也不可避免,但是对于这种动态变更高度的处理,本人认为最好不要直接在cellForRowAtIndexPat中进行排版,应该继承UITableViewCell生成子类,然后在子类里面进行排版工作。因为这跟下面说的解决方法有关。
先说说本人的解决思路吧,其实办法很简单,通过样本Cell来计算高度即可。也就是说我在定义Cell的子类后,在包含UITableView的控制器类或者视图类中定义一个Cell子类的属性,此属性就是专门用于在heightForRowAtIndexPath中计算Cell高度用。这样就能够正确地返回Cell的高度了。先来看看Cell的子类定义:
@interface DemoCell : NSObject{
UILabel *_content;
}
-(CGFloat)contentHeight;
-(void)setContent:(NSString *)content;
@end;
从上面的DemoCell来看其带有一个UILabel对象,这个Cell就是要根据UILabel的内容动态更改高度。其中contentHeight方法是返回Cell的高度。setContent是设置UILabel的内容并计算UILabel的高度。此类很简单要做的就是这样这些操作。然后我们在控制器中定义一个样本Cell专门用于计算Cell的高度。代码如下:
#import "DemoCell.h"
@interface DemoViewController : UIViewController<UITableViewDelegate,UITableViewDataSource>{
UITableView *_tableView;
DemoCell *_sampleCell;
NSArray *_listData;
}
@end;
其中部分实现代码如下:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//在此使用样本Cell计算高度。
NSString *content=[_listData objectAtIndex:indexPath.row];
[_sampleCell setContent:content];
return [_sampleCell contentHeight];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellId=@"DemoCell";
DemoCell *cell=(DemoCell *)[tableView dequeueReusableCellWithIdentifier:cellId];
if(cell==nil){
cell=[[[DemoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId] autorelease];
}
[cell setContent:[_listData objectAtIndex indexPath:indexPath.row]];
return cell;
}
eg: 二
有时我们需要动态调整UITableViewCell的高度,根据内容的不同设置不同的高度,以前看到一种实现方法,写得有点麻烦,具体地址找不到了,这里有个更好的(至少我认为),分享一下部分代码。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.tag = 1;
label.lineBreakMode = UILineBreakModeWordWrap;
label.highlightedTextColor = [UIColor whiteColor];
label.numberOfLines = 0;
label.opaque = NO; // 选中Opaque表示视图后面的任何内容都不应该绘制
label.backgroundColor = [UIColor clearColor];
[cell.contentView addSubview:label];
[label release];
}
UILabel *label = (UILabel *)[cell viewWithTag:1];
NSString *text;
text = [textArray objectAtIndex:indexPath.row];
CGRect cellFrame = [cell frame];
cellFrame.origin = CGPointMake(0, 0);
label.text = text;
CGRect rect = CGRectInset(cellFrame, 2, 2);
label.frame = rect;
[label sizeToFit];
if (label.frame.size.height > 46) {
cellFrame.size.height = 50 + label.frame.size.height - 46;
}
else {
cellFrame.size.height = 50;
}
[cell setFrame:cellFrame];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return cell.frame.size.height;
}
经验:cell最好用自定义的,可以按照你想要的结果来设计,效果很好,如图:
大概你第一眼看来,动态调整高度是一件不容易的事情,而且打算解决它的第一个想法往往是不正确的。在这篇文章中我将展示如何使图表单元格的高度能根据里面文本内容来动态改变,同时又不必子类化UITableViewCell。你当然可以通过子类化它来实现,但是这样做会使得代码复杂因为设置高度是在图表本身的实例上而不是对单元格操作。下面你将会看到这其实是一件轻而易举的事情。对于图表来说能够动态调整高度是件很有意义的事情,我首先想到的需要这个功能的是当显示一列长度会变化的文本列表时,如果文本内容较少,它或许能够适合正常的单元格label,但是如果文本变长,就不得不重新设置单元格大小以便于显示全部的文本内容。我总结了重新设置单元格大小的主要步骤如下:
1 创建并添加一个UILabel作为单元格cell的子视图;
2 在UITableView的委托方法: (CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath: (NSIndexPath *) indexPath中计算高度
3 在UITableView的委托方法: (UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath: (NSIndexPath *) indexPath中计算UILabel的框大小。
下面我要详细介绍这些步骤,首先看一下程序输出截图:
在普通的图表中,你可以简单地用下面的方法设置单元格内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 setMinimumFontSize: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];
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 setMinimumFontSize: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];
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]来使外框显示即可。这会有助于你了解绘图过程以及更快地在更深的层次理解绘图显示的问题。