Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reloadRowsAtIndexPaths:withRowAnimation: crashes my app

I got a strange problem with my UITableView: I use reloadRowsAtIndexPaths:withRowAnimation: to reload some specific rows, but the app crashes with an seemingly unrelated exception: NSInternalInconsistencyException - Attempt to delete more rows than exist in section.

My code looks like follows:

[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];

When I replace that reloadRowsAtIndexPaths:withRowAnimation: message with a simple reloadData, it works perfectly.

Any ideas?

like image 697
Anh Avatar asked Dec 16 '10 13:12

Anh


4 Answers

The problem is that you probably changed the number of items of your UITableView's data source. For example, you have added or removed some elements from/to the array or dictionary used in your implementation of the UITableViewDataSource protocol.

In that case, when you call reloadData, your UITableView is completely reloaded including the number of sections and the number of rows.

But when you call reloadRowsAtIndexPaths:withRowAnimation: these parameters are not reloaded. That causes the next problem: when you are trying to reload some cell, the UITableView checks the size of the datasource and sees that it has been changed. That results in a crash. This method can be used only when you want to reload the content view of the cell (for example, label has changed or you want to change its size).

Now if you want to remove/add cells from/to a UITableView you should use next approach:

  1. Inform the UITableView that its size will be changed by calling method beginUpdates.
  2. Inform about inserting new row(s) using method - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation.
  3. Inform about removing row(s) using method - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation.
  4. Inform the UITableView that its size has been changed by calling the method endUpdates.
like image 159
Nekto Avatar answered Nov 16 '22 06:11

Nekto


I think the following code might work:

[self.tableView beginUpdates];

[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];

[self.tableView endUpdates];
like image 42
Larry Avatar answered Nov 16 '22 07:11

Larry


I had this problem which was being caused by a block calling reloadRowsAtIndexPaths:withRowAnimation: and a parallel thread calling reloadData. The crash was due to reloadRowsAtIndexPaths:withRowAnimation finding an empty table even though I'd sanity checked numberOfRowsInSection & numberOfSections.

I took the attitude that I don't really care if it causes an exception. A visual corruption I could live with as a user of the App than have the whole app crash out.

Here's my solution to this which I'm happy to share and would welcome constructive criticism. If there's a better solution I'm keen to hear it?

- (void) safeCellUpdate: (NSUInteger) section withRow : (NSUInteger) row {
    // It's important to invoke reloadRowsAtIndexPaths implementation on main thread, as it wont work on non-UI thread
    dispatch_async(dispatch_get_main_queue(), ^{
        NSUInteger lastSection = [self.tableView numberOfSections];
        if (lastSection == 0) {
            return;
        }
        lastSection -= 1;
        if (section > lastSection) {
            return;
        }
        NSUInteger lastRowNumber = [self.tableView numberOfRowsInSection:section];
        if (lastRowNumber == 0) {
            return;
        }
        lastRowNumber -= 1;
        if (row > lastRowNumber) {
            return;
        }
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
        @try {
            if ([[self.tableView indexPathsForVisibleRows] indexOfObject:indexPath] == NSNotFound) {
                // Cells not visible can be ignored
                return;
            }
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }

        @catch ( NSException *e ) {
            // Don't really care if it doesn't work.
            // It's just to refresh the view and if an exception occurs it's most likely that that is what's happening in parallel.
            // Nothing needs done
            return;
        }
    });
}
like image 4
Seoras Avatar answered Nov 16 '22 08:11

Seoras


After many try, I found "reloadRowsAtIndexPaths" can be only used in certain places if only change the cell content not insert or delete cells. Not any place can use it, even you wrap it in

[self beginUpdates];
//reloadRowsAtIndexPaths
[self endUpdates];

The places I found that can use it are:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath - (IBAction) unwindToMealList: (UIStoryboardSegue *) sender

Any try from other places like call it from "viewDidLoad" or "viewDidAppear", either will not take effect (For the cell already loaded I mean, reload will not take effect) or cause exception.

So try to use "reloadRowsAtIndexPaths" only in those places.

like image 2
Weidian Huang Avatar answered Nov 16 '22 07:11

Weidian Huang