有没有办法更新单个UITableViewCell的高度,而无需重新计算每个单元格的高度?

时间:2013-10-15 06:36:20

标签: ios objective-c uitableview

我有一个带有几个不同部分的UITableView。一个部分包含将在用户将文本键入UITextView时调整大小的单元格。另一部分包含呈现HTML内容的单元格,计算高度相对较贵。

现在当用户输入UITextView时,为了让表视图更新单元格的高度,我调用

[self.tableView beginUpdates];
[self.tableView endUpdates];

然而,当我真的只需要更新输入的单个单元格时,这会导致表格重新计算表格中每个单元格的高度。不仅如此,而不是使用tableView:estimatedHeightForRowAtIndexPath:重新计算估计的高度,它会为每个单元格调用tableView:heightForRowAtIndexPath:,甚至是那些未显示的单元格。

有没有办法让表格视图更新单个单元格的高度,而不进行所有这些不必要的工作?

更新

我仍在寻找解决方案。正如所建议的那样,我已经尝试过使用reloadRowsAtIndexPaths:,但看起来这不会起作用。即使只有一行调用reloadRowsAtIndexPaths:仍然会导致每行调用heightForRowAtIndexPath:,即使只为您请求的行调用cellForRowAtIndexPath:。实际上,看起来每次插入,删除或重新加载行时,都会为表格单元格中的每一行调用heightForRowAtIndexPath:

我还尝试在willDisplayCell:forRowAtIndexPath:中放置代码来计算单元格出现之前的高度。为了使其工作,我需要强制表视图在我进行计算后重新请求行的高度。不幸的是,从[self.tableView beginUpdates]; [self.tableView endUpdates];调用willDisplayCell:forRowAtIndexPath:会导致UITableView内部代码中的索引超出范围异常。我猜他们不指望我们这样做。

我不禁觉得这是SDK中的一个错误,响应[self.tableView endUpdates]它没有为不可见的单元格调用estimatedHeightForRowAtIndexPath:,但我仍在尝试找到某种解决方法。任何帮助表示赞赏。

8 个答案:

答案 0 :(得分:29)

如上所述,reloadRowsAtIndexPaths:withRowAnimation:只会使表格视图向其UITableViewDataSource询问新的单元格视图,但不会要求UITableViewDelegate更新单元格高度。

不幸的是,只有通过调用

才能刷新高度
[tableView beginUpdates];
[tableView endUpdates];

即使两次通话之间没有任何变化。

如果您计算高度的算法太耗时,您应该缓存这些值。 类似的东西:

- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat height = [self cachedHeightForIndexPath:indexPath];

    // Not cached ?
    if (height < 0)
    {
        height = [self heightForIndexPath:indexPath];
        [self setCachedHeight:height
                 forIndexPath:indexPath];
    }

    return height;
}

确保在内容更改时或在初始时将这些高度重置为-1

编辑:

此外,如果您想尽可能延迟高度计算(直到它们滚动到),您应该尝试实现此功能(仅限iOS 7+):

@property (nonatomic) CGFloat estimatedRowHeight

  

提供行高的非负估计可以改善行   加载表视图的性能。如果表包含变量   高度行,计算所有高度时可能会很昂贵   桌子加载。使用估算可以让您推迟一些成本   从加载时间到滚动时间的几何计算。

     

默认值为0,表示没有估算值。

答案 1 :(得分:23)

此错误已在iOS 7.1中修复。

在iOS 7.0中,似乎没有办法解决这个问题。调用[self.tableView endUpdates]会导致为表中的每个单元调用heightForRowAtIndexPath:

但是,在iOS 7.1中,调用[self.tableView endUpdates]会导致为heightForRowAtIndexPath:调用可见单元格,并为不可见单元格调用estimatedHeightForRowAtIndexPath:

答案 2 :(得分:8)

可变行高会对您的表格视图性能产生非常不利的影响。您正在谈论在某些单元格中显示的Web内容。如果我们不是在谈论成千上万的行,考虑使用UIWebView而不是UITableView实现您的解决方案可能值得考虑。我们遇到了类似的情况,并使用带有自定义生成的HTML标记的UIWebView,它工作得很漂亮。您可能知道,当您拥有包含Web内容的动态单元格时,您会遇到令人讨厌的异步问题:

  • 设置好单元格的内容后,
  • 等到单元格中的Web视图完成呈现Web内容
  • 然后你必须进入UIWebView并使用JavaScript - 询问HTML文档有多高
  • 然后更新UITableViewCell的高度。

没有任何乐趣,并且为用户提供了大量的跳跃和抖动。

如果你必须使用UITableView,绝对缓存计算的行高。这样,在heightForRowAtIndexPath:中返回它们会很便宜。而不是告诉UITableView做什么,只需快速创建数据源。

答案 3 :(得分:2)

有办法吗? 答案是否定的。

您只能使用heightForRowAtIndexPath。 因此,您所能做的就是尽可能降低成本,例如在数据模型中保留单元格高度的NSmutableArray。

答案 4 :(得分:2)

我有一个类似的问题(在任何变化时跳过tableview的滚动)因为我有

  
      
  • (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {   返回500; }
  •   

评论整个功能有帮助。

答案 5 :(得分:1)

使用以下UITableView方法:

- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation

您必须指定要重新加载的NSIndexPath的NSArray。如果只想重新加载一个单元格,则可以提供仅包含一个NSIndexPath的NSArray。

NSIndexPath* rowTobeReloaded = [NSIndexPath indexPathForRow:1 inSection:0];
NSArray* rowsTobeReloaded = [NSArray arrayWithObjects:rowTobeReloaded, nil];
[UITableView reloadRowsAtIndexPaths:rowsTobeReloaded withRowAnimation:UITableViewRowAnimationNone];

答案 6 :(得分:0)

方法heightForRowAtIndexPath:将始终被调用,但这是我建议的解决方法。

每当用户输入UITextView时,请在本地变量中保存单元格的indexPath 。然后,在调用heightForRowAtIndexPath:时,验证已保存的indexPath的值。如果保存的indexPath不是nil,请检索应调整大小的单元格并执行此操作。对于其他单元格,请使用缓存的值。如果保存的indexPathnil,请执行您需要的常规代码行。

以下是我推荐的方法:

使用tag的属性UITextView来跟踪需要调整大小的行。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...

    [textView setDelegate:self];
    [textView setTag:indexPath.row];

    ...
}

然后,在UITextView委托方法textViewDidChange:中,检索indexPath并存储它。 savedIndexPath是一个局部变量。

- (void)textViewDidChange:(UITextView *)textView
{
    savedIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}

最后,检查savedIndexPath的值并执行它所需的内容。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (savedIndexPath != nil) {
        if (savedIndexPath == indexPath.row) {
            savedIndexPath = nil;

            // return the new height
        }
        else {

            // return cached value
        }
    }
    else {
        // your normal calculating methods...
    }
}

我希望这有帮助!祝你好运。

答案 7 :(得分:0)

我最终找到了解决问题的方法。我能够预先计算我需要渲染的HTML内容的高度,并包括高度以及数据库中的内容。这样,虽然当我更新任何单元格的高度时,我仍然被迫为所有单元格提供高度,但我不需要做任何昂贵的HTML渲染,所以它非常活泼。

不幸的是,只有在您预先获得所有HTML内容的情况下,此解决方案才有效。