51

我有一个 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];fromwillDisplayCell:forRowAtIndexPath:会导致 UITableView 内部代码深处出现索引越界异常。我猜他们不希望我们这样做。

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

4

8 回答 8

33

如前所述,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,表示没有估计值。

于 2013-10-23T08:40:26.133 回答
26

此错误已在 iOS 7.1 中修复。

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

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

于 2014-03-11T02:16:10.550 回答
8

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

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

一点也不好玩,而且给用户带来了很多跳跃和抖动。

如果您必须使用 UITableView,请务必缓存计算出的行高。这样退回它们会很便宜heightForRowAtIndexPath:。与其告诉 UITableView 做什么,不如让你的数据源快速。

于 2013-10-23T14:20:37.087 回答
2

有办法吗?答案是不。

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

于 2013-10-23T15:27:17.950 回答
2

我有一个类似的问题(在任何更改上跳表视图的滚动),因为我有

  • (CGFloat)tableView:(UITableView *)tableView 估计HeightForRowAtIndexPath:(NSIndexPath *)indexPath { return 500; }

评论整个功能有帮助。

于 2014-04-25T11:15:56.677 回答
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];
于 2013-10-15T07:24:09.640 回答
0

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

每当用户输入 时,将其保存在单元格UITextView的局部变量indexPath中。然后,当被调用时,验证已保存的值。如果保存的不是,则检索应调整大小的单元格并执行此操作。至于其他单元格,请使用您的缓存值。如果保存的是,请执行在您的情况下要求的常规代码行。heightForRowAtIndexPath:indexPathindexPathnilindexPathnil

我建议这样做:

使用 的属性tagUITextView跟踪需要调整哪一行的大小。

- (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...
    }
}

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

于 2013-10-27T03:49:42.373 回答
0

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

不幸的是,这个解决方案只有在您预先准备好所有 HTML 内容的情况下才有效。

于 2013-12-20T06:22:44.237 回答