I'm having a problem animating a change in rowHeight
for my tableview. The animation is taking a long time (about 3 seconds) on an iPad 3 (first gen retina). However, it only takes this long if I'm expanding the rowHeight
when the tableview is near the middle-to-bottom of the list or decreasing the rowHeight
in the middle of the list. When it's at the top, the animation works fine. Things to note:
UITableView
and overriding the setEditing:animated
method, which is where I change the rowHeight. tableView:heightForRowAtIndexPath:
as my table can have as many rows as the user wants. Some users may even have hundreds of thousands of rows if they imported data. 30.0f
), where I display a range of options for each cell (thinks like "delete", "copy", "print", "share", etc). beginUpdates
& endUpdates
) and instead simply call reloadData
, the change is instant. Of course, there's no animation then. [self reloadRowsAtIndexPaths:self.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationAutomatic]
to no effect (it takes forever). beginUpdates
& endUpdates
in just about every conceivable placement within the code, all to no effect. contentOffset
, but ended up with some weird effects, like tableCells not being refreshed, etc. tableView:cellForRowAtIndexPath:
. So I logged the number of times tableView:cellForRowAtIndexPath:
gets called during the animation:
scrollToRowAtIndexPath:atPosition:animated
: 59 times . scrollToRowAtIndexPath:atPosition:animated
: 67 times . reloadData
instead of beginUpdates
& endUpdates
: 8 times . Here's the code, which is pretty simple, really:
- (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];
}
It appears that when rowHeight
is changed, UITableView
calculates what new rows should be at the current contentOffset
and then attempt to animate to those new rows (including all rows in between), even though I'm telling it to move to the same cells it started out with. I suspect that if I were to increase the change in rowHeight (say, from 30.0f
to 45.0f
), the problem would grow worse as UITableView
would have to animate through even more rows for the change. What I want I need is for UITableView
to first move to the new cells and then animate the change for only those cells. However, I cannot seem to find a way to do this.
UPDATE
Holy {favorite_euphamism}! ... I've been trying to make tableView:cellForRowAtIndexPath
more efficient to no avail. So I ran a separate count of how many times tableView:cellForRowAtIndexPath
ends up creating new cells (rather than reuse them). During the animation, UITableView
isn't just requesting cells 59 - 67 times, it's creating 59-67 new cells by returning nil
for dequeueReusableCellWithIdentifier
. No wonder it's taking so long.... and it's spiking my memory as well (thank you Xcode 5 for displaying that...). While I've done as much as I can to make my cells efficient, they're still complex views and definitely not designed for that much creation. There's gotta be a way around this...
Any help or idea would be greatly appreciated. Thanks!
So, the solution I came up with is a hack at best, but it is working for the time being. It's possible that Apple has fixed this problem with the upcoming iOS 7.1 update according to Nikolai Ruhe , but I haven't messed with the beta yet so I can't confirm it.
Unfortunately, I was forced to implement tableView:heightForRowAtIndexPath:
. I didn't want to, but luckily most people have updated to iOS 7 and I can conditionally use the estimatedRowHeight
property (iOS 7 only) on UITableView
to mitigate the performance issues of large tables for iOS 7... every one else kind of gets screwed. I'm close to making my app "iOS 7 Only" so it won't be an issue for long (or for very many users).
I added 3 new iVars to my class: CGFloat _rowHeight
, CGFloat _imminentRowHeight
, and NSIndexPath *_anchorPath
. The _rowHeight
variable is returned by default. However, if _anchorPath
has been set, tableView:heightForRowAtIndexPath:
will conditionally return _imminentRowHeight
if indexPath.row >= _anchorPath.row
. This keeps the contentOffset
for the _anchorPath
the same during the transition so that UITableView
doesn't try to animated through several dozen tableview cells, which was the original problem:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if (_anchorPath){
return indexPath.row >= _anchorPath.row ? _imminentRowHeight : _rowHeight;
}
return _rowHeight;
}
I created a new method to separate out my logic, updateRowHeight
. I'd like to again point out this this is a subclass of UITableView
, so replace self
with your _tableView
iVar if you're doing this from a view controller:
- (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];
}
Some things to note:
UITableView
has to do a lot of work here, so it's best to not do the work unless necessary. CATransaction
to set the animation context so that I can attach a completion block to it. begin/endUpdates
will use the current animation context if one is available, else it will create its own... very convenient that. UITableView
thinks everything "above" the anchor path has the previous (and incorrect) row height. This is why I set _rowHeight = _imminentRowHeight;
and _anchorPath = nil;
before I reload the table. I must then scroll to the anchor path (not animated) after the reload because the offset of the anchor path has changed now that all previous rows have the new row height.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.