Reputation: 165
Ok so what I need is to prevent tableview scrolling when new elements added above current visible row. I'm using NSFetchedResultsController with a lot of objects (like 10 000) and reloadData works pretty smooth when I'm not touching contentOffset.
However, when I'm trying to manipulate contentOffset to keep scroll position when new entries are inserted it starts to freeze UI for like 300 ms which is bad for me.
Can anyone suggest any better (faster) solution of how to keep tableview at same cell when new items added at the top?
And this is the code that I'm using currently to track insertions and shift contentOffset. It doesn't support animations and different cell size.
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
switch(type) {
case NSFetchedResultsChangeInsert:
[_insertions addObject:@(newIndexPath.row)];
break;
}
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
_insertions = @[].mutableCopy;
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
NSMutableArray *copy = _insertions.mutableCopy;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSSortDescriptor *lowestToHighest = [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES];
[copy sortUsingDescriptors:[NSArray arrayWithObject:lowestToHighest]];
dispatch_sync(dispatch_get_main_queue(), ^{
for (NSNumber *i in copy) {
[self p_delayedAddRowAtIndex:[i integerValue]];
}
[self.tableView reloadData];
});
});
}
- (CGFloat)p_firstRowHeight {
return [self tableView:[self tableView] heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0
inSection:0]];
}
-(void)p_delayedAddRowAtIndex:(NSInteger)row {
NSIndexPath *firstVisibleIndexPath = [[self.tableView indexPathsForVisibleRows] firstObject];
if (firstVisibleIndexPath.row >= row) {
CGPoint offset = [[self tableView] contentOffset];
offset.y += [self firstRowHeight];
if ([self.tableView visibleCells].count > self.tableView.frame.size.height / [self firstRowHeight]) {
[[self tableView] setContentOffset:offset];
}
}
}
Upvotes: 8
Views: 3590
Reputation: 165
Ok, after some responses which pointed me in right direction I came up with this solution. It works smooth though does some ugly coordinates math and doesn't support different cell sizes. Hope it will be helpful for someone.
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
NSIndexPath *firstVisible = [[self.tableView indexPathsForVisibleRows] firstObject];
self.topVisibleMessage = [self.fetchedResultsController objectAtIndexPath:firstVisible];
NSIndexPath *topIndexPath = [self.fetchedResultsController indexPathForObject:self.topVisibleMessage];
self.cellOffset = self.tableView.contentOffset.y - topIndexPath.row * [self firstRowHeight];
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView reloadData];
if (self.topVisibleMessage) {
NSIndexPath *topIndexPath = [self.fetchedResultsController indexPathForObject:self.topVisibleMessage];
CGPoint point = {0, topIndexPath.row * [self firstRowHeight] + self.cellOffset};
[self.tableView setContentOffset:point];
}
}
Upvotes: 2
Reputation: 3003
Your task is maybe familiar with lots of newsfeed application list views. Did you look at those applications. They might have some kind of solutions. As I know, the only way to keep the table view position is scrollRectToVisible:animated:
.
Many social network applications have familiar approach to insert new cells on top. When there are updates small notification at view top appears with message ("5 new items"), which on tap inserts cells and auto scrolls to top.
Upvotes: -1