我在为表视图设置rowHeight
更改动画时遇到问题。这部动画在iPad3(第一代视网膜)上花费了很长时间(约3秒)。但是,如果当表视图接近列表的中间到底部时,我扩展rowHeight
,或者减少列表中间的rowHeight
,则只需要这么长时间。当它位于顶部时,动画效果良好。需要注意的事项:
- 我正在对
UITableView
进行子类化,并覆盖setEditing:animated
方法,这是我更改rowHeight的地方 - 我尽量不使用
tableView:heightForRowAtIndexPath:
,因为我的表可以有用户想要的任意多的行。如果一些用户导入数据,他们甚至可能拥有数十万行 - 当用户将表格置于"编辑"模式时,我希望行高增加一定的量(目前为
30.0f
),其中我为每个单元格显示一系列选项(比如"删除"、"复制"、"打印"、"共享"等) - 因为行高度在变化,所以我在rowHeight变化之前获取显示的最顶部单元格的路径,然后在rowHeith变化之后将表视图滚动到适当的路径,这样用户就不会失去位置。如果我根本不滚动,延迟会稍微好一点(也许是2.5秒,而不是3秒),列表最终会出现一个与最初完全不同的地方
- 如果我不为更改设置动画(又名:
beginUpdates
&endUpdates
),而是简单地调用reloadData
,则更改是即时的。当然,那时没有动画 - 我试过使用
[self reloadRowsAtIndexPaths:self.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationAutomatic]
,但没有效果(它需要很长时间) - 我试着把
beginUpdates
&endUpdates
在代码中几乎每一个可以想象的位置,都没有任何效果 - 我尝试过计算并直接设置
contentOffset
,但最终出现了一些奇怪的效果,比如tableCells没有刷新等等 - 我做了一些时间档案,发现大部分时间都花在了
tableView:cellForRowAtIndexPath:
上。所以我记录了tableView:cellForRowAtIndexPath:
在动画过程中被调用的次数:- 无
scrollToRowAtIndexPath:atPosition:animated
:59次 - CCD_ 18:67次
- 使用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.0f
到45.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 _rowHeight
、CGFloat _imminentRowHeight
和NSIndexPath *_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
的一个子类,所以如果您是从视图控制器进行操作,请用_tableView
iVar替换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];
}
需要注意的一些事项:
- 如果不需要更新,我会提前退出该方法。如果行高没有更改,或者没有找到anchorPath,则会发生这种情况,如果没有显示行或者表尚未加载,则会出现这种情况。
UITableView
在这里要做很多工作,所以除非必要,否则最好不要做这些工作 - 我使用
CATransaction
来设置动画上下文,这样我就可以给它附加一个完成块。begin/endUpdates
将使用当前的动画上下文(如果有),否则它将创建自己的。。。非常方便 - 我将锚点路径滚动(设置动画)到顶部位置,以便在重新加载表数据时表视图不会"跳转"。同样,在重新加载数据后,我再次滚动(没有动画),使表看起来与重新加载前完全相同
- 我必须重新加载表格,因为我只更改了位于锚路径"下方"的单元格的行高度。在我重新加载之前,
UITableView
认为锚路径"上方"的所有内容都具有以前(且不正确)的行高度。这就是我在重新加载表之前设置_rowHeight = _imminentRowHeight;
和_anchorPath = nil;
的原因。然后,我必须在重新加载后滚动到锚定路径(未设置动画),因为锚定路径的偏移量已经更改,因为所有以前的行都具有新行高度 - 当表格重新加载时,会有一个非常轻微的"闪烁"。至少对我的应用程序来说,只有单元格文本"闪烁",这是因为我使用的是异步绘制文本的自定义绘图例程。因此,对于其他人来说,可能根本不会有任何闪烁。对我来说,效果太小了,我可能会忽略它。我正在考虑显示覆盖的想法,但由于我们谈论的是一整屏的内容,我怀疑它是否能很好地工作