smparkes
smparkes

Reputation: 14063

insertRowsAtIndexPaths calling cellForRowAtIndexPath for every row

I'm trying to insert a bunch of rows into an empty UITableView in one step using insertRowsAtIndexPaths. (I know that this doesn't sound all that useful, but it's a simplified version of what I really need to do.)

The issue I'm seeing is that after I make the insertRowsAtIndexPaths call, I get cellForRowAtIndexPath calls for every row I inserted, rather than just those that are visible.

That doesn't seem right. And it's pretty much useless to me if it does this.

The only slightly odd other artifact I see is that when I do this, it actually seems to animate the rows into place. This isn't the withRowAnimation, since that's set to none. But it seems there's some sort of higher level animation concept going on here. I had the off-idea that as it animated the rows into place it thought it needed cells for more/all the rows until they got pushed off screen. But I can't turn off this animation without just using reloadData which I'd prefer to avoid.

Generally I have a data set that changes behind the scenes. I can carefully go through and construct the changes to generate data for the insert/delete/reload calls. But I can't limit that to what's on screen since then it doesn't match the data provider.

I figure I must be missing something ... any ideas?

(I am doing this within a beginUpdates/endUpdates pair but that seems to make no difference.)

Upvotes: 3

Views: 5484

Answers (8)

Anirudha Mahale
Anirudha Mahale

Reputation: 2596

For swift you can try this

self.mainTableView.beginUpdates()
    self.mainTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Fade)
    self.mainTableView.reloadRowsAtIndexPaths(self.mainTableView.indexPathsForVisibleRows!, withRowAnimation: UITableViewRowAnimation.Fade)
    self.mainTableView.endUpdates()

Upvotes: 0

Tim
Tim

Reputation: 343

I was able to solve this by combining a few answers from here:

set the massive load boolean as needed as suggested by pocjoc

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    UITableView *tableView = self.tableView;
    switch(type) {
        case NSFetchedResultsChangeInsert:
            massiveLoad = YES;
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
        ...
    }
}

and reload the visible rows when it ends

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
    if (massiveLoad) {
        massiveLoad = NO;
        [self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationFade];
    }
}

Upvotes: 2

pocjoc
pocjoc

Reputation: 510

I had the same problem, what I did was to declare an attribute called bMassiveLoad initialized to false. Before I do the 'massive load' I set it to true and the first thing I do in the cellForRowAtIndexPath was to check for that attribute. At the end of the 'maseive load' I set it to false again and call reloadData...

I know that it is not a beautiful answer, but it helps me.

Upvotes: 2

smparkes
smparkes

Reputation: 14063

I talked to some Apple folks today. The issue is definitely related to the animation that UITableView does when you insert new rows. This is the animation that happens to create new rows for cells, separate from the animation that is used when those rows are brought in via insertRowsAtIndexPaths.

Since the table-level animation creates new rows through a sort of "grow down" kind of thing, during the insert animation small amounts of many cells are considered visible and the table will call cellForRowAtIndexPath for them.

There doesn't seem to be any alternative implementation for this that works for all cases. The recommended solution for my case is to "lie": figure out what cells will exist on page after the animation and insert those. After that animation completes, insert the remainder of cells that won't show and the table won't call cellForRowAtIndexPath.

This requires implementing an object between the table view and the "real" data source that can "lie" to the table view about the number of rows in the table for the duration of the animation. Painful, but that's apparently what has been done in similar cases.

Upvotes: 5

justin
justin

Reputation: 5831

I feel like there may be a different way to go about this, but what I have found is that if you have existing cells in the visible range (for example, if you have 10 cells visible at all times and you initialize the tableView with 10 blank cells), when you add new rows, the extra rows are not called in cellForRowAtIndexPath.

So, what to do about it. This is how I went about fixing this. When I created the tableView, I added cells to the visible region. They were blank. Then, when I inserted new rows, I only added index paths for the extra rows (i.e. if I have 10 visible rows and want to add 50 rows, including the visible rows, I only added 40 since the first 10 already exist). This of course leaves you with 10 blank cells. At this point, you can use:

NSArray *indexPaths = [myTableView indexPathsForVisibleCells];
[myTableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];

This selects your 10 visible blank cells and reloads them with the new information you have passed in (I assume you load data into an array for this part). You will need to add a little logic to your cellForRowAtIndexPath method such as:

if (![myDataArray count]) {
    // create a blank cell;
} else {
    // add context to your cells as you want;
}

This should do the trick for you and only load the cells you have showing on the screen

Upvotes: 0

AechoLiu
AechoLiu

Reputation: 18378

The UITableView will request the visible cells by cellForRowAtIndexPath: method, and it can reduce the processing time when there are many cells, ex 1k cells, and reduce spent memory by reusable cell mechanism. If you need to process all cells, then you should implement a custom method to re-process your source data, and then call [tableView reloadData] to update the UI.

Upvotes: -1

sergio
sergio

Reputation: 69027

I don't know whether the behavior you describe is correct, but UITableView has got two methods: beginUpdates and endUpdates (here) that are meant to optimize redrawing (so that animating is done only at the end, otherwise they are done one by one and this could possibly produce the call to cellForRowAtIndexPath).

Now, I am not sure if this could help you, but perhaps you can give this a try...

Upvotes: 0

timthetoolman
timthetoolman

Reputation: 4623

UITableView calls the relevant delegate and data source methods immediately after a call to insertRowsAtIndexPaths to get the cells and other content for visible cells. This is regardless of whether or not animated is set to YES or NO.

See the discussion here

If you only want the tableView to update the visible cells, then just update your dataSource and call [tableView reloadData].

Upvotes: 0

Related Questions