UITableView rowHeight动画耗时过长



我在为表视图设置rowHeight更改动画时遇到问题。这部动画在iPad3(第一代视网膜)上花费了很长时间(约3秒)。但是,如果当表视图接近列表的中间到底部时,我扩展rowHeight,或者减少列表中间的rowHeight,则只需要这么长时间。当它位于顶部时,动画效果良好。需要注意的事项:

  1. 我正在对UITableView进行子类化,并覆盖setEditing:animated方法,这是我更改rowHeight的地方
  2. 我尽量不使用tableView:heightForRowAtIndexPath:,因为我的表可以有用户想要的任意多的行。如果一些用户导入数据,他们甚至可能拥有数十万行
  3. 当用户将表格置于"编辑"模式时,我希望行高增加一定的量(目前为30.0f),其中我为每个单元格显示一系列选项(比如"删除"、"复制"、"打印"、"共享"等)
  4. 因为行高度在变化,所以我在rowHeight变化之前获取显示的最顶部单元格的路径,然后在rowHeith变化之后将表视图滚动到适当的路径,这样用户就不会失去位置。如果我根本不滚动,延迟会稍微好一点(也许是2.5秒,而不是3秒),列表最终会出现一个与最初完全不同的地方
  5. 如果我不为更改设置动画(又名:beginUpdates&endUpdates),而是简单地调用reloadData,则更改是即时的。当然,那时没有动画
  6. 我试过使用[self reloadRowsAtIndexPaths:self.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationAutomatic],但没有效果(它需要很长时间)
  7. 我试着把beginUpdates&endUpdates在代码中几乎每一个可以想象的位置,都没有任何效果
  8. 我尝试过计算并直接设置contentOffset,但最终出现了一些奇怪的效果,比如tableCells没有刷新等等
  9. 我做了一些时间档案,发现大部分时间都花在了tableView:cellForRowAtIndexPath:上。所以我记录了tableView:cellForRowAtIndexPath:在动画过程中被调用的次数:
    1. scrollToRowAtIndexPath:atPosition:animated:59次
    2. CCD_ 18:67次
    3. 使用CCD_ 19代替CCD_;endUpdates:8次

这是代码,非常简单,真的:

- (void) setEditing:(BOOL)editing animated:(BOOL)animated{
CGPoint point = CGPointMake(1, _sectionView.superview ? _sectionView.bounds.size.height + 1 + self.bounds.origin.y : 1 + self.bounds.origin.y);
NSIndexPath *path = [self indexPathForRowAtPoint:point];
[super setEditing:editing animated:animated];
CGFloat rowHeight = self.viewType.viewHeight.floatValue + (self.editing ? FlxRecordTableCellEditingHeight : 0);
self.rowHeight = rowHeight;
[self beginUpdates];
[self endUpdates];
//    [self reloadData];
[self scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:NO];
//    [self reloadRowsAtIndexPaths:self.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationAutomatic];
}

看起来,当rowHeight发生更改时,UITableView会计算出当前contentOffset应该有哪些新行,然后尝试对这些新行(包括其间的所有行)进行动画处理,尽管我告诉它移动到最初使用的单元格。我怀疑,如果我增加rowHeight的更改(例如,从30.0f45.0f),问题会变得更糟,因为UITableView将不得不通过更多的行来进行更改。我想要的是UITableView首先移动到新的单元格,然后仅为这些单元格设置更改的动画。然而,我似乎找不到这样做的方法。

更新

神圣的{偏袒_euphamism}。。。我一直在努力提高tableView:cellForRowAtIndexPath的效率,但没有成功。因此,我单独计算了tableView:cellForRowAtIndexPath最终创建新单元格(而不是重用它们)的次数。在动画过程中,UITableView不仅仅请求单元格59-67次,它还通过为dequeueReusableCellWithIdentifier返回nil来创建新单元格。难怪要花这么长时间。。。。它也增加了我的记忆(感谢Xcode 5显示了这一点…)。虽然我已经尽我所能使我的单元格高效,但它们仍然是复杂的视图,绝对不是为这么多创建而设计的。一定有办法解决这个问题。。。

任何帮助或想法都将不胜感激。谢谢

所以,我提出的解决方案充其量只是一个破解,但它暂时有效。根据Nikolai Ruhe的说法,苹果可能已经在即将到来的iOS 7.1更新中解决了这个问题,但我还没有搞砸测试版,所以我无法确认。

不幸的是,我被迫实现了tableView:heightForRowAtIndexPath:。我不想,但幸运的是,大多数人都更新到了iOS 7,我可以有条件地在UITableView上使用estimatedRowHeight属性(仅限iOS 7)来缓解iOS 7的大表的性能问题……其他的都有点糟糕。我即将把我的应用程序制作成"仅限iOS 7",所以它不会成为一个长期的问题(或者对很多用户来说)。

我在类中添加了3个新的iVar:CGFloat _rowHeightCGFloat _imminentRowHeightNSIndexPath *_anchorPath。默认情况下会返回_rowHeight变量。但是,如果已经设置了_anchorPath,则如果indexPath.row >= _anchorPath.row,则tableView:heightForRowAtIndexPath:将有条件地返回_imminentRowHeight。这使contentOffset_anchorPath在转换过程中保持不变,这样UITableView就不会试图通过几十个表视图单元设置动画,这就是最初的问题:

- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if (_anchorPath){
return indexPath.row >= _anchorPath.row ? _imminentRowHeight : _rowHeight;
}
return _rowHeight;
}

我创建了一个新的方法来分离我的逻辑updateRowHeight。我想再次指出,这是UITableView的一个子类,所以如果您是从视图控制器进行操作,请用_tableViewiVar替换self

- (void) updateRowHeight{
CGFloat cellHeight = self.viewType.viewHeight.floatValue;
CGPoint anchorPoint = CGPointMake(1, _sectionView.superview ? _sectionView.bounds.size.height + 1 + self.bounds.origin.y : 1 + self.bounds.origin.y);
_anchorPath = [self indexPathForRowAtPoint:anchorPoint];
_imminentRowHeight = cellHeight + (self.editing ? FlxRecordTableCellEditingHeight : 0);
if (!_anchorPath){
_rowHeight = _imminentRowHeight;
if ([self respondsToSelector:@selector(estimatedRowHeight)]){
self.estimatedRowHeight = _rowHeight;
}
return;
}
if (_imminentRowHeight == _rowHeight) return;
[CATransaction begin];
[CATransaction setCompletionBlock:^{
_rowHeight = _imminentRowHeight;
NSIndexPath *anchor = _anchorPath;
_anchorPath = nil;
if ([self respondsToSelector:@selector(estimatedRowHeight)]){
self.estimatedRowHeight = _rowHeight;
}
[self reloadData];
[self scrollToRowAtIndexPath:anchor atScrollPosition:UITableViewScrollPositionTop animated:NO];
}];
[self beginUpdates];
[self endUpdates];
//We must scroll the anchor Path into the top position so that when we reload the table data positions don't change
[self scrollToRowAtIndexPath:_anchorPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
[CATransaction commit];
}

需要注意的一些事项:

  1. 如果不需要更新,我会提前退出该方法。如果行高没有更改,或者没有找到anchorPath,则会发生这种情况,如果没有显示行或者表尚未加载,则会出现这种情况。UITableView在这里要做很多工作,所以除非必要,否则最好不要做这些工作
  2. 我使用CATransaction来设置动画上下文,这样我就可以给它附加一个完成块。begin/endUpdates将使用当前的动画上下文(如果有),否则它将创建自己的。。。非常方便
  3. 我将锚点路径滚动(设置动画)到顶部位置,以便在重新加载表数据时表视图不会"跳转"。同样,在重新加载数据后,我再次滚动(没有动画),使表看起来与重新加载前完全相同
  4. 我必须重新加载表格,因为我只更改了位于锚路径"下方"的单元格的行高度。在我重新加载之前,UITableView认为锚路径"上方"的所有内容都具有以前(且不正确)的行高度。这就是我在重新加载表之前设置_rowHeight = _imminentRowHeight;_anchorPath = nil;的原因。然后,我必须在重新加载后滚动到锚定路径(未设置动画),因为锚定路径的偏移量已经更改,因为所有以前的行都具有新行高度
  5. 当表格重新加载时,会有一个非常轻微的"闪烁"。至少对我的应用程序来说,只有单元格文本"闪烁",这是因为我使用的是异步绘制文本的自定义绘图例程。因此,对于其他人来说,可能根本不会有任何闪烁。对我来说,效果太小了,我可能会忽略它。我正在考虑显示覆盖的想法,但由于我们谈论的是一整屏的内容,我怀疑它是否能很好地工作

最新更新