ios 有没有办法更新单个 UITableViewCell 的高度,而无需重新计算每个单元格的高度?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19374699/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a way to update the height of a single UITableViewCell, without recalculating the height for every cell?
提问by Chris Vasselli
I have a UITableView with a few different sections. One section contains cells that will resize as a user types text into a UITextView. Another section contains cells that render HTML content, for which calculating the height is relatively expensive.
我有一个包含几个不同部分的 UITableView。一个部分包含将在用户将文本键入 UITextView 时调整大小的单元格。另一部分包含呈现 HTML 内容的单元格,为此计算高度的成本相对较高。
Right now when the user types into the UITextView, in order to get the table view to update the height of the cell, I call
现在当用户输入 UITextView 时,为了让表格视图更新单元格的高度,我调用
[self.tableView beginUpdates];
[self.tableView endUpdates];
However, this causes the table to recalculate the height of everycell in the table, when I really only need to update the singlecell that was typed into. Not only that, but instead of recalculating the estimated height using tableView:estimatedHeightForRowAtIndexPath:
, it calls tableView:heightForRowAtIndexPath:
for every cell, even those not being displayed.
但是,这会导致表格重新计算表格中每个单元格的高度,而我实际上只需要更新输入的单个单元格。不仅如此,tableView:estimatedHeightForRowAtIndexPath:
它还tableView:heightForRowAtIndexPath:
没有使用 重新计算估计高度,而是调用每个单元格,甚至那些未显示的单元格。
Is there any way to ask the table view to update just the height of a single cell, without doing all of this unnecessary work?
有没有办法让表格视图只更新单个单元格的高度,而无需做所有这些不必要的工作?
Update
更新
I'm still looking for a solution to this. As suggested, I've tried using reloadRowsAtIndexPaths:
, but it doesn't look like this will work. Calling reloadRowsAtIndexPaths:
with even a single row will still cause heightForRowAtIndexPath:
to be called for every row, even though cellForRowAtIndexPath:
will only be called for the row you requested. In fact, it looks like any time a row is inserted, deleted, or reloaded, heightForRowAtIndexPath:
is called for every row in the table cell.
我仍在寻找解决方案。按照建议,我已经尝试使用reloadRowsAtIndexPaths:
,但看起来这行不通。reloadRowsAtIndexPaths:
即使只为您请求的行调用heightForRowAtIndexPath:
,即使cellForRowAtIndexPath:
只调用一行仍会导致对每一行调用。事实上,看起来heightForRowAtIndexPath:
每次插入、删除或重新加载行时,都会为表格单元格中的每一行调用。
I've also tried putting code in willDisplayCell:forRowAtIndexPath:
to calculate the height just before a cell is going to appear. In order for this to work, I would need to force the table view to re-request the height for the row after I do the calculation. Unfortunately, calling [self.tableView beginUpdates]; [self.tableView endUpdates];
from willDisplayCell:forRowAtIndexPath:
causes an index out of bounds exception deep in UITableView's internal code. I guess they don't expect us to do this.
我还尝试在willDisplayCell:forRowAtIndexPath:
单元格出现之前输入代码来计算高度。为了使它起作用,我需要在计算后强制表视图重新请求行的高度。不幸的是,调用[self.tableView beginUpdates]; [self.tableView endUpdates];
fromwillDisplayCell:forRowAtIndexPath:
会导致 UITableView 内部代码深处的索引越界异常。我猜他们不希望我们这样做。
I can't help but feel like it's a bug in the SDK that in response to [self.tableView endUpdates]
it doesn't call estimatedHeightForRowAtIndexPath:
for cells that aren't visible, but I'm still trying to find some kind of workaround. Any help is appreciated.
我不禁觉得这是 SDK 中的一个错误,响应[self.tableView endUpdates]
它并没有调用estimatedHeightForRowAtIndexPath:
不可见的单元格,但我仍在尝试找到某种解决方法。任何帮助表示赞赏。
采纳答案by Chris Vasselli
This bug has been fixed in iOS 7.1.
此错误已在 iOS 7.1 中修复。
In iOS 7.0, there doesn't seem to be any way around this problem. Calling [self.tableView endUpdates]
causes heightForRowAtIndexPath:
to be called for every cell in the table.
在 iOS 7.0 中,似乎没有办法解决这个问题。Calling[self.tableView endUpdates]
导致heightForRowAtIndexPath:
为表格中的每个单元格调用。
However, in iOS 7.1, calling [self.tableView endUpdates]
causes heightForRowAtIndexPath:
to be called for visible cells, and estimatedHeightForRowAtIndexPath:
to be called for non-visible cells.
但是,在 iOS 7.1 中,调用[self.tableView endUpdates]
会导致heightForRowAtIndexPath:
对可见单元格的estimatedHeightForRowAtIndexPath:
调用和对不可见单元格的调用。
回答by Rivera
As noted, reloadRowsAtIndexPaths:withRowAnimation:
will only cause the table view to ask its UITableViewDataSource
for a new cell view but won't ask the UITableViewDelegate
for an updated cell height.
如前所述,reloadRowsAtIndexPaths:withRowAnimation:
只会导致表格视图要求其UITableViewDataSource
提供新的单元格视图,而不会要求UITableViewDelegate
更新的单元格高度。
Unfortunately the height will only be refreshed by calling:
不幸的是,高度只能通过调用来刷新:
[tableView beginUpdates];
[tableView endUpdates];
Even without any change between the two calls.
即使两次调用之间没有任何变化。
If your algorithm to calculate heights is too time consuming maybe you should cache those values. Something like:
如果您计算高度的算法太耗时,也许您应该缓存这些值。就像是:
- (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;
}
And making sure to reset those heights to -1
when the contents change or at init time.
并确保将这些高度重置-1
为内容更改时或初始时间。
Edit:
编辑:
Also if you want to delay height calculation as much as possible (until they are scrolled to) you should try implementing this (iOS 7+ only):
此外,如果您想尽可能延迟高度计算(直到它们被滚动到),您应该尝试实现这一点(仅限 iOS 7+):
@property (nonatomic) CGFloat estimatedRowHeight
@property (nonatomic) CGFloat estimatedRowHeight
Providing a nonnegative estimate of the height of rows can improve the performance of loading the table view. If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.
The default value is 0, which means there is no estimate.
提供行高的非负估计可以提高加载表视图的性能。如果表格包含可变高度的行,则在加载表格时计算它们的所有高度可能会很昂贵。使用估计允许您将几何计算的一些成本从加载时间推迟到滚动时间。
默认值为 0,表示没有估计。
回答by Johannes Fahrenkrug
Variable row heights have a very negative impact on your table view performance. You are talking about web content that is displayed in some of the cells. If we are not talking about thousands of rows, thinking aboutimplementing your solution with a UIWebView instead of a UITableView might be worth considering. We had a similar situation and went with a UIWebView with custom generated HTML markup and it worked beautifully. As you probably know, you have a nasty asynchronous problem when you have a dynamic cell with web content:
可变行高对您的表视图性能有非常负面的影响。您正在谈论显示在某些单元格中的 Web 内容。如果我们不是在谈论数千行,那么考虑使用 UIWebView 而不是 UITableView 来实现您的解决方案可能值得考虑。我们遇到了类似的情况,并使用了带有自定义生成的 HTML 标记的 UIWebView,它工作得很好。您可能知道,当您有一个包含 Web 内容的动态单元格时,您会遇到一个令人讨厌的异步问题:
- After setting the content of the cell you have to
- wait until the web view in the cell is done rendering the web content,
- then you have to go into the UIWebView and - using JavaScript - ask the HTML document how high it is
- and THEN update the height of the UITableViewCell.
- 设置单元格的内容后,您必须
- 等到单元格中的 Web 视图完成呈现 Web 内容,
- 然后你必须进入 UIWebView 并 - 使用 JavaScript - 询问 HTML 文档它有多高
- 然后更新 UITableViewCell 的高度。
No fun at all and lots of jumping and jittering for the user.
根本没有乐趣,用户有很多跳跃和抖动。
If you do have to go with a UITableView, definitely cachethe calculated row heights. That way it will be cheap to return them in heightForRowAtIndexPath:
. Instead of telling the UITableView what to do, just make your data source fast.
如果你必须使用 UITableView,一定要缓存计算出的行高。这样一来,将它们退回将很便宜heightForRowAtIndexPath:
。与其告诉 UITableView 做什么,不如让你的数据源快速。
回答by ader
Is there a way? The answer is no.
有办法吗?答案是不。
You can only use heightForRowAtIndexPath for this. So all you can do is make this as inexpensive as possible by for example keeping an NSmutableArray of your cell heights in your data model.
您只能为此使用 heightForRowAtIndexPath。因此,您所能做的就是通过例如在数据模型中保留单元格高度的 NSmutableArray 来尽可能降低成本。
回答by soprof
I had a similar issue(jumping scroll of the tableview on any change) because I had
我有一个类似的问题(在任何更改时跳转 tableview 滚动),因为我有
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { return 500; }
- (CGFloat)tableView:(UITableView *)tableView EstimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { return 500; }
commenting the entire function helped.
评论整个功能有帮助。
回答by Nilesh Patel
Use the following UITableView method:
使用以下 UITableView 方法:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
You have to specify an NSArray of NSIndexPath which you want to reload. If you want to reload only one cell, then you can supply an NSArray that holds only one NSIndexPath.
您必须指定要重新加载的 NSIndexPath 的 NSArray。如果您只想重新加载一个单元格,那么您可以提供一个仅包含一个 NSIndexPath 的 NSArray。
NSIndexPath* rowTobeReloaded = [NSIndexPath indexPathForRow:1 inSection:0];
NSArray* rowsTobeReloaded = [NSArray arrayWithObjects:rowTobeReloaded, nil];
[UITableView reloadRowsAtIndexPaths:rowsTobeReloaded withRowAnimation:UITableViewRowAnimationNone];
回答by jbrodriguez
The method heightForRowAtIndexPath:
will always be called but here's a workaround that I would suggest.
该方法heightForRowAtIndexPath:
将始终被调用,但这是我建议的一种解决方法。
Whenever the user is typing in the UITextView
, save in a local variable the indexPath
of the cell. Then, when heightForRowAtIndexPath:
is called, verify the value of the saved indexPath
. If the saved indexPath
isn't nil
, retrieve the cellthat should be resized and do so. As for the other cells, use your cached values. If the saved indexPath
is nil
, execute your regular lines of code which in your case are demanding.
每当用户输入 时UITextView
,保存在单元格的局部变量indexPath
中。然后,当被调用时,验证所保存的值。如果保存的不是,则检索应调整大小的单元格并执行此操作。至于其他单元格,请使用您的缓存值。如果保存的是,请执行在您的情况下要求的常规代码行。heightForRowAtIndexPath:
indexPath
indexPath
nil
indexPath
nil
Here's how I would recommend doing it:
以下是我建议的做法:
Use the property tag
of UITextView
to keep track of which row needs to be resized.
使用属性tag
的UITextView
跟踪其中的行需要调整大小。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
[textView setDelegate:self];
[textView setTag:indexPath.row];
...
}
Then, in your UITextView
delegate's method textViewDidChange:
, retrieve the indexPath
and store it. savedIndexPath
is a local variable.
然后,在您的UITextView
委托的方法中textViewDidChange:
,检索indexPath
并存储它。savedIndexPath
是局部变量。
- (void)textViewDidChange:(UITextView *)textView
{
savedIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}
Finally, check the value of savedIndexPath
and execute what it's needed.
最后,检查 的值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...
}
}
I hope this helps! Good luck.
我希望这有帮助!祝你好运。
回答by Chris Vasselli
I ended up figuring out a way to work around the problem. I was able to pre-calculate the height of the HTML content I need to render, and include the height along with the content in the database. That way, although I'm still forced to provide the height for all cells when I update the height of any cell, I don't have to do any expensive HTML rendering so it's pretty snappy.
我最终找到了解决这个问题的方法。我能够预先计算我需要呈现的 HTML 内容的高度,并将高度与数据库中的内容一起包含在内。这样,虽然我在更新任何单元格的高度时仍然被迫提供所有单元格的高度,但我不必进行任何昂贵的 HTML 渲染,因此它非常活泼。
Unfortunately, this solution only works if you've got all your HTML content up-front.
不幸的是,此解决方案仅在您预先获得所有 HTML 内容时才有效。