Insertion and deletion of rows in UITableView sometimes causes the crash? - ios

I am doing very usual stuff.
First modifying the data model and then updating the TableView. But sometimes app crashes.
And throws error something like that (depends on insertion or deletion).
Fatal Exception: NSInternalInconsistencyException
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (10) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
Here is my insertion code.
[self.tableView beginUpdates];
[self.stories insertObject:story atIndex:0];
NSArray *indexPaths = #[[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
and Deletion code.
[tableview beginUpdates];
[stories removeObject:story];
NSIndexPath *indexpath = [tableview indexPathForCell:cell];
if(indexpath) {
[tableview deleteRowsAtIndexPaths:#[indexpath] withRowAnimation:UITableViewRowAnimationTop];
}
[tableview endUpdates];

Insertion Code:
[self.stories insertObject:story atIndex:0];
[self.tableView beginUpdates];
NSArray *indexPaths = #[[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
Deletion Code:
[stories removeObject:story];
[tableview beginUpdates];
NSIndexPath *indexpath = [tableview indexPathForCell:cell];
[tableview deleteRowsAtIndexPaths:#[indexpath] withRowAnimation:UITableViewRowAnimationTop];
[tableview endUpdates];
Note: You should not add/remove object to an array inside updating your tableview's rows.

Either do [tableview reloadData]; or [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
Also you are doing database operation in-between [self.tableView beginUpdates];
and [self.tableView endUpdates];. It's not proper.
As per the error suggests, there is a mismatch in table view rows and data source of that table view.

Related

UITableView insert row breaks scroll

I am trying to insert row at the top of the table. Firstly insert into the data:
[self.myDataArray insertObject:scannedItem atIndex:0];
Then I call reloadData for the table. Item inserted but table scrolls up, item doesn't visible and it is possible to scroll all items out of the top bound. I have no dynamic cells and also have estimatedRowHeight set as const integer(60).
Here is screens for tableview
[self.tableView beginUpdates];
[self.myDataArray insertObject:scannedItem atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:indexPath inSection:0]]
withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];

Adding a row to a tableView not working

I have a custom UITableView I'm trying to add a row to the tableView. Here is my code:
- (IBAction)addRow:(id)sender
{
NSInteger indexPath = [self.rowArray count] - 1;
// I'm adding an object to an array of all the cell ids I have.
[self.rowArray insertObject:#"rowSix" atIndex:indexPath]; // rowSix is a cell ID
[self.myTableView beginUpdates];
[self.myTableView endUpdates];
}
This is the error that it outputs:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (8) must be equal to the number of rows contained in that section before the update (7), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Update
In cellForRowAtIndexPath:
NSString *cellID = [self.rowArray objectAtIndex:[indexPath row]];
customCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
Try this one-
//Update data source with the object that you need to add
[tableDataSource addObject:newObject];
NSInteger row = //specify a row where you need to add new row
NSInteger section = //specify the section where the new row to be added,
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
try this -
- (IBAction)addRow:(id)sender
{
[self.rowArray addObject:#"New Item"];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:[self.rowArray count] inSection:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}

UITableView insertRowsAtIndexPaths remove cell separator

I need to add a new row under the selected row. I'm using this code in didSelectRowAtIndexPath:
[_dataSource insertObject:newDataObject atIndex:indexPath.row + 1];
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger currentCount = indexPath.row;
[indexPaths addObject:[NSIndexPath indexPathForRow:currentCount+1 inSection:0]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
It adds the new row in the correct position, but it also deletes the upper cell separator of the selected row. What am I doing wrong?
Seems like UITableViewCell has no separator in selected state. UITableViewCellSelectionStyleNone just disables highlighting, but cell is selected anyway. adding
[tableView beginUpdates];
// insert/delete manipulations
[tableView deselectRowAtIndexPath:indexPath animated:NO];
[tableView endUpdates];
fixes the problem

UITableView - No Items Label - using third cell like Notes App - Crashes when inserting the first record and deleting the last record

I would like to implement the same behavior as was done in iOS6 with the notes App, perhaps the same is done with iOS7 I can't remember.
To display the message "No Notes" in the third cell, I have done that just fine, however when I add the first record or delete the last record it crashes.
I don't want to use the table header or footer, I would like to do it the same way as done in the Notes app please.
Similar to this question
UITableView deleteRowsAtIndexPath crash when delete last record
so the problem is it crashes when inserting the first record or deleting the last record. The error message is the same:-
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
I understand the problem and why it happens but I have tried too many different things to resolve it but I didn't know how.
Can someone kindly please help me. Thank and much appreciated.
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.myModel.count == 0)
{
return 3;
}
return self.myModel.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Identifier" forIndexPath:indexPath];
if (self.allNotes.count == 0)
{
cell = [self initilizeNoItemsCell:cell forIndexPath:indexPath];
}
else
{
cell = [self initilizeMyObjectCell:cell forIndexPath:indexPath];
}
return cell;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
switch(type)
{
case NSFetchedResultsChangeInsert:
//crashes here when inserting the first record
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadData];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)tableView:(UITableView *)tableViewObj commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
//Crashes here also when deleting the last record
[MyModel deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
NSError *error;
if (![MyModel save:&error])
{
NSLog(#"Failed to delete - error: %#", [error localizedDescription]);
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert)
{
}
}
Ho do I get rid of the fake 3 rows before I add a new record?
Update 1:-
When inserting a second record.
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
When deleting a record (not the last record)
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to insert row 1 into section 0, but there are only 1 rows in section 0 after the update with userInfo (null)
After getting Paul's answer, its getting me closer. It crashes when I try to add a second record. Also when deleting anything but the last record.
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
NSArray *emptyIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
nil];
switch(type)
{
case NSFetchedResultsChangeInsert:
if (self.allNotes.count == 1)
{
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:emptyIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
break;
case NSFetchedResultsChangeDelete:
if (self.allNotes.count == 1)
{
[tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:emptyIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadData];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
Thanks,
When you delete the last row then the expected row count will be 0 (1 row - 1 row deleted = 0), but your numberOfRowsInSection will return 3 - because self.myModel.count is now 0.
A similar thing will occur when you insert the first row - 4 is the expected row count (3 + 1), but 1 is the value from numberOfRowsInSection.
You need -
NSArray *emptyIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
nil];
case NSFetchedResultsChangeInsert:
[tableView beginUpdates];
if (self.myModel.count == 1) {
[tableView deleteRowsAtIndexPaths:emptyIndexPaths withRowAnimation:UITableViewRowAnimationFade];
}
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
break;
case NSFetchedResultsChangeDelete:
[tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
if (self.myModel.count == 0) {
[tableView insertRowsAtIndexPaths:emptyIndexPaths withRowAnimation:UITableViewRowAnimationFade];
}
[tableView endUpdates];
break;

UITableView correct animation

The matter is, I want to have a grouped table view, and when I click on a cell, I want to have an another cell to appear under it, and so on. Everything is fine with workflow, but I have same problems with animations. It works very slowly and I can see all changes
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"selected section:%d selected row:%d",indexPath.section,indexPath.row);
NSIndexPath* pathToDelete;
[_tableView beginUpdates];
if (_selectedPath != nil && [_selectedPath isEqual:indexPath]){
//If the same row was selected
pathToDelete = [NSIndexPath indexPathForRow:_selectedPath.row+1 inSection:_selectedPath.section];
_selectedPath = nil;
[_tableView beginUpdates];
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:pathToDelete] withRowAnimation:UITableViewRowAnimationTop];
[_tableView endUpdates];
} else {
//If not
if (_selectedPath != indexPath){
//delete the row that we selected last time
if (_selectedPath != nil){
pathToDelete = [NSIndexPath indexPathForRow:_selectedPath.row inSection:_selectedPath.section];
[_tableView beginUpdates];
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:pathToDelete] withRowAnimation:UITableViewRowAnimationTop];
[_tableView endUpdates];
}
//add new row in the section we selected
_selectedPath = indexPath;
[_tableView beginUpdates];
[_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[_tableView endUpdates];
}
}
[_tableView beginUpdates];
[_tableView deselectRowAtIndexPath:indexPath animated:NO];
[_tableView endUpdates];
[_tableView reloadSections:[[NSIndexSet alloc] initWithIndex:pathToDelete.section] withRowAnimation:UITableViewRowAnimationMiddle];
[_tableView reloadSections:[[NSIndexSet alloc] initWithIndex:_selectedPath.section] withRowAnimation:UITableViewRowAnimationMiddle];
[_tableView endUpdates];
}
I think the problem is with begin & endUpdates method, but I don't know where exactly. Any clue?
Thanks
You could do this much simpler, by using a single beginUpdates - endUpdates. You just make your deletions and insertions inside of it. Don`t forget to update the data source for the table, otherwise the app will crash.
Hope this helps!

Resources