关于uitableview:我怎样才能在iPhone上正确地做可变高度表格单元格?

How can I do variable height table cells on the iPhone properly?

我的应用需要具有可变高度的表格单元格(因为每个表格单元格的高度不同,而不是每个单元格都需要能够自行调整大小)。

我有一个目前有效的解决方案,但它很笨拙而且速度很慢。

我目前的解决方案:

在表格单元格被渲染之前,我通过在其数据上调用诸如 -sizeWithFont:constrainedToSize: 之类的大小调整方法来计算每个单元格需要多高。然后我将高度相加,允许一些填充并将结果与??数据一起存储。

然后,当我的 UITableViewDelegate 收到 -tableview:heightForRowAtIndexPath: 时,我会计算出将为该单元格呈现哪个项目并返回我之前计算的高度。

正如我所说,这是可行的,但是当您按顺序为数百个项目执行此操作时,调用 -sizeWithFont:constrainedToSize: 非常慢,我觉得可以做得更好。

因此,为了使其工作,我必须维护两部分代码 - 一部分用于计算单元格高度,另一部分用于在时机成熟时实际绘制单元格。

如果模型项发生任何变化,我必须更新这两个代码块,但有时它们仍然不能完美匹配,有时会导致表格单元格对于给定的项目,或太大。

我提出的解决方案:

所以我想取消预先计算单元格高度。 A) 因为它打破了 MVC 范式和 B) 因为它很慢。

所以我的单元格自行绘制,结果,单元格高度正确。我的问题是我无法在绘制之前告诉表格视图单元格的高度 - 到那时为时已晚。

我尝试从 -tableview:heightForRowAtIndexPath: 中调用 -cellForRowAtIndexPath: ,但这会陷入无限循环,因为第一个会在某些时候调用第二个,反之亦然(至少这是我在尝试时看到的)。

所以这个选项是不可能的。

如果我没有为行委托方法指定高度的大小,那么表格视图就会变得混乱。单元格是完美的高度,但它们的 x 位置是固定高度的单元格的位置。

混乱的表格单元格 http://jamsoftonline.com/images/messed_table_cells.png

注意底部单元格的大小是正确的 - 它只是与前一个单元格重叠,前一个单元格与其前一个单元格重叠,依此类推。

同样使用这种方法,在滚动时会出现一些伪影,我认为这可能与单元格的重用标识符有关。

因此,我们将不胜感激。


这是我使用的。 NSString 有一个方法可以根据字体信息和您给它的高度/宽度约束告诉您文本框的尺寸。

1
2
3
4
5
6
7
8
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *text = [self getTextForIndexPath:indexPath];
    UIFont *font = [UIFont systemFontOfSize:14];
    CGSize size = [self getSizeOfText:text withFont:font];

    return (size.height + 11); // I put some padding on it.
}

然后你写一个方法来拉取这个单元格的文本...

1
2
3
4
5
6
7
- (NSString *)getTextForIndexPath:(NSIndexPath *)indexPath
{
    NSString *sectionHeader = [self.tableSections objectAtIndex:[indexPath section]];
    NSString *sectionContent = [self.tableData objectForKey:sectionHeader];

    return sectionContent;
}

这是获取文本的大小。

1
2
3
4
- (CGSize)getSizeOfText:(NSString *)text withFont:(UIFont *)font
{
    return [text sizeWithFont:font constrainedToSize:CGSizeMake(280, 500)];
}


只是一个想法:

  • 如果你有六种不同类型的单元格,每个单元格都有自己的标识符和固定的高度。一个用于单行电池,另一个用于两行电池,等等...

  • 每次您的模型发生变化时,计算该行的高度,然后找到高度最接近您需要的最近的单元格类型。将该单元类型标识符与模型一起保存。您还可以在模型中存储该单元格的固定行高,以便您可以在 tableview:heightForRowAtIndexPath 调用中返回它(我不会太担心强制它在单元格类本身内部进行计算——从技术上讲,它不是一部分的单元格绘图功能和更多的东西 tableview 用来决定创建哪种单元格类型)。

  • 在运行时,当被要求为该行返回一个单元格时,您需要做的就是创建(或从单元格缓存中获取)具有单元格类型标识符的单元格,加载值,然后就可以开始了。

如果单元格高度计算太慢,那么您可以采用与 tableview 缓存相同的技巧,仅在单元格进入视图时按需执行。在任何给定时间,您只需对视图中的单元格执行此操作,然后只需对单个单元格执行此操作,因为它在任一端滚动到视图中。


由于您提到的无限循环,我意识到这对您不起作用,但是我在调??用单元格 layoutSubViews 方法方面取得了一些成功

虽然由于对 cellForRowAtIndexPath 和 layoutSubViews 的多次调用,这可能有点低效,但我发现代码更简洁。

1
2
3
4
5
6
7
8
-(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MyCell *cell = (MyCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];

    [cell layoutSubviews];

    return CGRectGetHeight(cell.frame);
}

并在布局代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)layoutSubviews
{
    [super layoutSubviews];

    //First expand the label to a large height to so sizeToFit isn't constrained
    [self.myArbitrarilyLengthLabel setFrame:CGRectMake(self.myArbitrarilyLengthLabel.frame.origin.x,
                                          self.myArbitrarilyLengthLabel.frame.origin.y,
                                          self.myArbitrarilyLengthLabel.frame.size.width,
                                          1000)];


    //let sizeToFit do its magic
    [self.myArbitrarilyLengthLabel sizeToFit];

    //resize the cell to encompass the newly expanded label
    [self setFrame:CGRectMake(self.frame.origin.x,
                             self.frame.origin.y,
                             self.frame.size.width,
                              CGRectGetMaxY(self.myArbitrarilyLengthLabel.frame) + 10)];
}