Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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];
        }
    }
}
like image 619
kandreych Avatar asked Nov 21 '14 10:11

kandreych


People also ask

How to scroll to the bottom of a table in UITableView?

UITableView has the scrollToRow method built in. It takes 3 arguments. The first one will be the IndexPath that we created, the second will be where you want the table to to stop, this can be top, bottom, middle or none.

What is the difference between UITableView and ScrollView in Salesforce?

In a UITableView, you can quickly reload, insert, delete, and reorder rows. All these actions come with standard animations out of the box. In a scroll view, you have to write a ton of code to get the same functionality.

What are the cells of UITableView?

The cells of UITableView are instances of UITableViewCell or its subclasses. It is the table view that adds, removes, and arranges cells in its view hierarchy. Table views reuse cells that go out of the screen to display new elements, so that:

What is the difference between Scroll View and Table View?

In a scroll view, you have to write a ton of code to get the same functionality. A table view can arrange items into sections and use indexes. Moreover, the section headers remain on the screen until the whole section passes. And indexes allow the user to quickly jump around a table view with a lot of content.


1 Answers

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];
    }
}
like image 177
kandreych Avatar answered Sep 22 '22 07:09

kandreych