22

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?

Anh
  • 6,523
  • 7
  • 46
  • 59
  • 1
    Check similar question: http://stackoverflow.com/questions/3542260/how-can-i-increase-the-height-of-uitableviewcell-dynamiclly/3542289 may be it will be helpful – Vladimir Dec 16 '10 at 14:30

9 Answers9

32

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.
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Nekto
  • 17,837
  • 1
  • 55
  • 65
  • Theoretically makes sense but doesn't work . I am trying to reload sections and it still crashes. I am using estimated header and cell height with UITableViewAutomaticDimension for heights,Its a requirement. – sheetal Aug 04 '16 at 19:14
  • @sheetal, if you take the approach in this answer you don't have to call reloadRowsAtIndexPaths:withRowAnimation:. Basically add a row to your model data structure (an array in most cases). Then call beginUpdate, insertRowsAtIndexPaths and endUpdates. – RajV Aug 29 '17 at 18:40
  • I'm prefetching images and then need to update the row when prefetched image has finished loading. This causes a crash if, for example, the user has scrolled to the bottom and more posts are loaded into the feed. I don't understand how I can possibly prevent the crash - begin/end updates not working, running all modifying functions on the main thread doesn't work. I'm stumped. – JCutting8 Sep 22 '22 at 06:38
9

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];
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Larry
  • 107
  • 1
  • 5
  • this one solved my problem.I was using core data, and i had passed a different fetch request to the resultfetchcontroller and reloaded the table once the fetch was finished.Even though the fetch was successfull the app crashed.But using the above method the reload was successfull. – prajul Mar 11 '12 at 18:53
  • incorrect. beginUpdates/endUpdates is not required in case of only one reloadRowsAtIndexPath, but will never fix crash if this is an attempt to reload a newly added row. Check Nekto's anwer for details. – Vitalii Aug 22 '17 at 10:48
  • Doesn't work for me. I also don't understand why begin/end updates needs to be called because you're not modifying the number of rows in the tableview by using `reloadRowsAtIndexPaths` – JCutting8 Sep 22 '22 at 06:44
4

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;
        }
    });
}
Seoras
  • 1,286
  • 13
  • 21
  • In Swift, do-try-catch says that no functions in the block throw any exceptions so the catch statement will never be reached. – JCutting8 Sep 22 '22 at 06:50
2

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.

Weidian Huang
  • 2,787
  • 2
  • 20
  • 29
2

You should check cell visibility before reload. Here is Swift 3 code:

        let indexPath = IndexPath(row: offset, section: 0)
        let isVisible = tableView.indexPathsForVisibleRows?.contains{$0 == indexPath}
        if let v = isVisible, v == true {
            tableView.reloadRows(at: [indexPath], with: .automatic)
        }
Bill Chan
  • 3,199
  • 36
  • 32
  • 1
    Had the exact same issue, and this was the resolution: If the indexPath is invisible I'm using `reloadData` instead. – zaltzy Mar 20 '19 at 09:49
1

THIS IS OLD. DO NOT USE. I just bumped into this issue when I was calling reloadRowsAtIndexPaths... in order to change the cell to an editing cell containing a UITextField. The error told me I was deleting all of the rows in the table. To solve the problem, I removed:

[self.tableView beginUpdates];
NSArray *reloadIndexPath = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:count inSection:section]];
[self.tableView reloadRowsAtIndexPaths:reloadIndexPath withRowAnimation:UITableViewRowAnimationFade];  
[self.tableView endUpdates];

and replaced it with

[self.tableView reloadData];
user216661
  • 323
  • 3
  • 14
1

I had the same issue. In my case; it was happening only if another view controller pop/pushed over existing table view controller and then[self.tableView reloadRowsAtIndexPaths] function is called.

reloadRowsAtIndexPaths call was hiding/showing different rows in a table view which is having over 30, visually complex, rows. As i try to fix the issue i found that if i slightly scroll the table view app wasn't crashing. Also it wasn't crashing if i don't hide a cell (by returning 0 as height)

To resolve the issue, i simply changed the "(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath" function and returned at least 0.01 as row height.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
return rowModel.height + 0.01; // Add 0.01 to work around the crash issue.
}

it solved the issue for me.

Oguz Demir
  • 169
  • 2
  • 6
0

The app crashes because you have made some changes to your tableView. Either you have added or deleted some rows to the tableView. Hence when the view controller asks your model controller class for data, there is a mismatch in the indexPaths. Since the indexPaths have changed after modification.

So either you simply remove the call

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

or replace it with

[self.tableView reloadData];

Calling reloadData checks your number of sections, number of rows in each section and then reloads the whole thing.

iPhoneDeveloper
  • 958
  • 1
  • 14
  • 23
0

If data count changes completely, then use reloadData else, there is three functions to do it.

When data count changes we use insertRows / deleteRows and when data count still the same use reloadRows.

Important! don't forget call beginUpdates and endUpdates between insertRows/deleteRows/reloadRows calls.

Artyom
  • 1
  • 1
  • 1