I have a tableView that I\'m inserting rows into at the top.
Whilst I\'m doing this I want the current view to stay completely still, so the rows only appear if you
@Dean,
You can change your code like this to prevent animating.
[tableView beginUpdates];
[UIView setAnimationsEnabled:NO];
// ...
[tableView endUpdates];
[tableView setContentOffset:newOffset animated:NO];
[UIView setAnimationsEnabled:YES];
had the same problem and found a solution.
save tableView currentOffset. (Underlying scrollView method)
//Add rows (beginUpdates,insert...,endUpdates) // don't do this!
reloadData ( to force a recalulation of the scrollview size )
add newly inserted row heights to contentOffset.y here, using tableView:heightForRowAtIndexPath:
setContentOffset (Underlying scrollview method)
like this:
- (CGFloat) firstRowHeight
{
return [self tableView:[self tableView] heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
...
CGPoint offset = [[self tableView] contentOffset];
[self tableView] reloadData];
offset.y += [self firstRowHeight];
if (offset.y > [[self tableView] contentSize].height) {
offset.y = 0;
}
[[self tableView] setContentOffset:offset];
...
works perfectly, without glitches.
@Dean's way of using an image cache is too hacky and I think it destroys the responsiveness of the UI.
One proper way: Use a UITableView subclass and override -setContentSize: in which you can by some means calculate how much the table view is pushed down and offset that by setting contentOffset.
This is a simplest sample code to handle the simplest situation where all insertions happen at the top of table view:
@implementation MyTableView
- (void)setContentSize:(CGSize)contentSize {
// I don't want move the table view during its initial loading of content.
if (!CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
if (contentSize.height > self.contentSize.height) {
CGPoint offset = self.contentOffset;
offset.y += (contentSize.height - self.contentSize.height);
self.contentOffset = offset;
}
}
[super setContentSize:contentSize];
}
@end
I want add additional condition. If your code in iOS11 or more, you need do like below;
In iOS 11, table views use estimated heights by default. This means that the contentSize is just as estimated value initially. If you need to use the contentSize, you’ll want to disable estimated heights by setting the 3 estimated height properties to zero:
tableView.estimatedRowHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0
I did some testing with a core data sample project and got it to sit still while new cells were added above the top visible cell. This code would need adjustment for tables with empty space on the screen, but once the screen is filled, it works fine.
static CGPoint delayOffset = {0.0};
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller {
if ( animateChanges )
[self.tableView beginUpdates];
delayOffset = self.tableView.contentOffset; // get the current scroll setting
}
Added this at cell insertion points. You may make counterpart subtraction for cell deletion.
case NSFetchedResultsChangeInsert:
delayOffset.y += self.tableView.rowHeight; // add for each new row
if ( animateChanges )
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
and finally
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if ( animateChanges )
{
[self.tableView setContentOffset:delayOffset animated:YES];
[self.tableView endUpdates];
}
else
{
[self.tableView reloadData];
[self.tableView setContentOffset:delayOffset animated:NO];
}
}
With animateChanges = NO, I could not see anything move when cells were added.
In testing with animateChanges = YES, the "judder" was there. It seems the animation of cell insertion did not have the same speed as the animated table scrolling. While the result at the end could end with visible cells exactly where they started, the whole table appears to move 2 or 3 pixels, then move back.
If the animation speeds could be make to equal, it may appear to stay put.
However, when I pressed the button to add rows before the previous animation finished, it would abruptly stop the animation and start the next, making an abrupt change of position.
Everyone loves copy and pasting code examples, so here's an implementation of Andrey Z.'s answer.
This is in my delegateDidFinishUpdating:(MyDataSourceDelegate*)delegate
method
if (self.contentOffset.y <= 0)
{
[self beginUpdates];
[self insertRowsAtIndexPaths:insertedIndexPaths withRowAnimation:insertAnimation];
[self endUpdates];
}
else
{
CGPoint newContentOffset = self.contentOffset;
[self reloadData];
for (NSIndexPath *indexPath in insertedIndexPaths)
newContentOffset.y += [self.delegate tableView:self heightForRowAtIndexPath:indexPath];
[self setContentOffset:newContentOffset];
NSLog(@"New data at top of table view");
}
The NSLog
at the bottom can be replaced with a call to show a view that indicated there's fresh data.