kandreych
kandreych

Reputation: 165

Make UITableView keep scroll position when adding cells at top

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

Answers (2)

kandreych
kandreych

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

Daniyar
Daniyar

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

Related Questions