UITableView deleteRowsAtIndexPath when to update the model? - objective-c

I'm implementing a method to remove a row from my tableview based on a domain object but I'm getting a error after call deleteRowsAtIndexPath:animated
Blockquote 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 (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).'
So I thought "ok, I'm updating the model too soon". The code:
- (void)deleteItem:(Item *)item {
NSInteger index = [items indexOfObject:item]; // items is the model a NSArray
if (index != NSNotFound) {
NSMutableArray *itemEditable = [items mutableCopy];
[itemEditable removeObjectAtIndex:index];
self.items = itemEditable;
NSIndexPath *indexToDelete = [NSIndexPath indexPathForRow:index inSection:0];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexToDelete] withRowAnimation:UITableViewRowAnimationMiddle];
}
}
And then I change the code to update the model after I call deleteRowsAtIndexPath:animated
- (void)deleteItem:(Item *)item {
NSInteger index = [items indexOfObject:item]; // items is the model a NSArray
if (index != NSNotFound) {
NSIndexPath *indexToDelete = [NSIndexPath indexPathForRow:index inSection:0];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexToDelete] withRowAnimation:UITableViewRowAnimationMiddle];
NSMutableArray *itemEditable = [items mutableCopy];
[itemEditable removeObjectAtIndex:index];
self.items = itemEditable;
}
}
But it didn't solve, the only thing that changes is the number of rows in the error message from 2 to 3.
Blockquote 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 (3) 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 (0 inserted, 1 deleted).'
Where should I update the model?
Added numberOfRowsInSection method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [items count];
}

I've found the problem. The setter of items property is implemented to call reloadData and this was causing this confusion.
-(void) setItens:(NSMutableArray *)newItens{
if (itens != nil) {
[itens release];
itens = nil;
}
itens = [newItens copy];
// [tableView reloadData]; // WRONG don't do this
}
This is the second time I found what I was doing wrong just after publishing the question here. I hope this help someone one day. Thanks aBitObvious, I'm aBitShy now :)

I think after deleting the row you need to reload it just below the deleted line:
I mean,
- (void)deleteItem:(Item *)item
{
.......................your code..............
[tableView reloadData];
}
Try this, it should work.

Related

Selecting Objects from NSArray for further deleting with an IBAction

So I am trying to select objects from my array, to be able to delete them when I do an IBAction. I tried:
checking if the item is Selected:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.editEnabled) {
RDNote *selectedNote = [self.notes objectAtIndex:indexPath.row];
if (selectedNote.isSelected) {
selectedNote.selected = NO;
for (NSIndexPath *indexPathFromArray in self.indexPathsOfSelectedCells) {
if (indexPathFromArray.row == indexPath.row) {
[self.mutableCopy removeObject:indexPathFromArray];
}
}
} else {
selectedNote.selected = YES;
[self.indexPathsOfSelectedCells addObject:indexPath];
}
[self.collectionView reloadData];
IBAction:
- (IBAction)didTapTrashBarButton:(id)sender {
NSMutableArray *mutableNotes = [NSMutableArray arrayWithArray:self.notes];
for (NSIndexPath *indexPath in self.indexPathsOfSelectedCells) {
[mutableNotes removeObjectAtIndex:indexPath.row];
}
self.notes = [NSArray arrayWithArray:mutableNotes];
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithArray:self.indexPathsOfSelectedCells]];
} completion:^(BOOL finished) {
self.indexPathsOfSelectedCells = nil;
[self activateEditMode:NO];
[self saveDataToFile:self.notes];
}];
}
But I am having issues with indexes e.g.:(some times shows me the Error that Object index 2 is not between [0..1]), and there are bugs while selecting multiple Object and Deleting them.
Please help me with an advice for some other method that I can use, a Code will be perfect! Thanks!
This problem arrises because:
Let say you have five objects in array 1,2,3,4,5
you are running a loop for removing object based on indexpath which are selected rows. Now your indexpath contains 1st row and 3rd row.
while executing it for the first time you will remove object 1. Now 2,3,4,5 will left in the array. Now second time your indexpath.row is 3rd. It will remove third object which is 4 but in actual array it was 3.
Your code is crashing sometimes because if you have selected first row and last row. In this case i have selected 1 and 5. Now my indexpaths array will say I have to delect objectsAtIndexes 1 and 5.
While executing the loop I'll remove object at Index 1. Now i will be left with 2,3,4,5. On second iteration it will say remove objectAtIndex 5 where as Index 5 doesn't exist because now we have 4 elements in array.
In such cases best way of doing this is try removing elements in array from the end like remove 5th element first and then go for other one. Run your loop in reverse order.
NSInteger i = [self.indexPathsOfSelectedCells count]-1;
while (i > 0){
NSIndexPath *indexPath = [self.indexPathsOfSelectedCells objectAtIndex:i];
[mutableNotes removeObjectAtIndex:indexPath.row];
i--;
}

NSInternalInconsistency Exception in UITableView

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString* CellIdentifier = #"MessageCellIdentifier";
MessageTableViewCell* cell = (MessageTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[MessageTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
Message* message = [self.dataModel.messages objectAtIndex:indexPath.row];
[cell setMessage:message];
return cell;
}
I am developing an chat application in which i am getting exception when sending and receiving messages happening at the same time.The following is the exception message
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2380.17/UITableView.m:1070 2013-04-30
16:55:14.314 [2689:907] 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 (19) must be equal to the number of rows contained in that section before the update (17), 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).'
- (int)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.dataModel.messages count];
}
- (void)didSaveMsg:(NSMutableArray *)array1
{
self.dataModel.messages = array1;
[tableview insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:array1.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
[self scrollToNewestMessage];
}
- (void)scrollToNewestMessage
{
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void){
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:(self.dataModel.messages.count - 1) inSection:0];
[tableview scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
});
[tableview reloadData];
[[DBModel database]updateMessageCount1:mobileNo cnt:#"0"];
}
The error message is fairly self explanatory - you are telling the table view you want to insert a single cell, but in the table's datasource are then saying it has two extra cells to display (19 instead of 17).
The problem almost certainly isn't in the code you've posted, it will be either in the numberOfRows method of your datasource, or in the code at the point at which you insert the extra cell.

Moving a row between sections

I have a problem with my moveRowAtIndexPath:toIndexPath code.
It works fine when I'm moving cells within a section but when I try to move a row from one section (say from indexPath [1,1] to indexPath [2,0]) to another I get a crash with this error message:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 1. 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 (2), 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, 1 moved out).'
Here is my code, again, it works fine if the two indexPaths share the same section number:
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
NSMutableArray *cells = [[self cellsAtSectionOfIndexPath:indexPath] mutableCopy];
Cell *cell = [self dataAtIndexPath:indexPath];
Cell *newCell = [self dataAtIndexPath:newIndexPath];
if ([indexPath section] == [newIndexPath section])
{
[cells replaceObjectAtIndex:indexPath.row withObject:newCell];
[cells replaceObjectAtIndex:newIndexPath.row withObject:cell];
[self replaceCellsAtIndexPath:indexPath withCells:cells];
}
else
{
NSMutableArray *newCells = [[self cellsAtSectionOfIndexPath:newIndexPath] mutableCopy];
[cells replaceObjectAtIndex:indexPath.row withObject:newCell];
[newCells replaceObjectAtIndex:newIndexPath.row withObject:cell];
[self replaceCellsAtIndexPath:indexPath withCells:cells];
[self replaceCellsAtIndexPath:newIndexPath withCells:newCells];
}
[super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
}
How can I fix this?
The problem is with your data source. The error means that you are removing a row from a section but the numberOfRowsInSection: method returns the same number when it should return the old number minus 1. Same thing with adding the row to the other section.

NSInternalInconsistencyException with UITableViewController and a Storyboard-Pop

I integrated GrabKit in my iPhone-App, which is a Library to Grab Photos from social Networks. Now I have the following situation/problem:
I have a UITableViewController - AlbumListViewController.
Then there's a UITableViewCell - AlbumCellViewController.
When you tap on one of these cells - albums with photos
There's a UITableViewController - PhotoListViewController
and a UITableViewCell - PhotoCellViewController.
Everything here works just finde, I can browse albums, choose one, browse the photos in it and then when I choose one of the single photos, there is a PushViewController to my SetPhotoViewController, which displays the selected image in Fullscreen.
Now when I want to use the back-Button of my navigation bar in the SetPhotoViewController, the crashes with the following message:
*** Assertion failure in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:], /SourceCache/UIKit_Sim/UIKit-2372/UITableViewRowData.m:400
2012-10-22 22:49:32.814 Amis[1820:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to allocate data stores for 1073741821 rows in section 1. Consider using fewer rows'
*** First throw call stack:
(0x23de012 0x1b63e7e 0x23dde78 0x15f9f35 0xc8f83b 0xc923c4 0xb56fa2 0xb5692c 0x1690f 0x1cd653f 0x1ce8014 0x1cd87d5 0x2384af5 0x2383f44 0x2383e1b 0x2a9a7e3 0x2a9a668 0xaab65c 0x42fd 0x2b75)
libc++abi.dylib: terminate called throwing an exception
DataSource and Delegate of my two UITableViewControllers are both set to File's Owner.
When I debug trough the code, I can't find any special things, all photos can be loaded, all the arrays are filled with data, there's nothing strange.
Here are the two functions of the PhotoListViewController which get called, as soon as I press the back button on my NavigationController:
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
NSLog(#"%i", indexPath.row);
NSLog(#"%ii", indexPath.section);
// Extra Cell
if ( indexPath.section > _lastLoadedPageIndex ){
static NSString *extraCellIdentifier = #"ExtraCell";
cell = [tableView dequeueReusableCellWithIdentifier:extraCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:extraCellIdentifier];
}
cell.textLabel.text = #"load more";
cell.textLabel.font = [UIFont fontWithName:#"System" size:8];
} else {
static NSString *photoCellIdentifier = #"photoCell";
cell = [tableView dequeueReusableCellWithIdentifier:photoCellIdentifier];
if (cell == nil) {
cell = [[[NSBundle mainBundle] loadNibNamed:#"PhotoRowViewController" owner:self options:nil] objectAtIndex:0];
}
}
// setting of the cell is done in method [tableView:willDisplayCell:forRowAtIndexPath:]
return cell;
}
and..
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// extra cell
if ( [cell.reuseIdentifier isEqualToString:#"ExtraCell"] ){
if ( state == GRKDemoPhotosListStateAllPhotosGrabbed ) {
cell.textLabel.text = [NSString stringWithFormat:#" %d photos", [[_album photos] count] ];
}else {
cell.textLabel.text = [NSString stringWithFormat:#"Loading page %d", _lastLoadedPageIndex+1];
[self fillAlbumWithMorePhotos];
}
} else // Photo cell
{
NSArray * photosAtIndexPath = [self photosForCellAtIndexPath:indexPath];
[(PhotoRowViewController*)cell setPhotos:photosAtIndexPath];
}
}
What am I missing?
Edit: The code of the numberOfRowsInSection-Method:
If I debug it, the first time res is equal 0, the second time the method gets called, res is equal 322121535.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSUInteger res = 0;
if ( state == GRKDemoPhotosListStateAllPhotosGrabbed && section == _lastLoadedPageIndex ) {
NSUInteger photosCount = [_album count];
// Number of cells with kNumberOfPhotosPerCell photos
NSUInteger numberOfCompleteCell = (photosCount - section*kNumberOfRowsPerSection*kNumberOfPhotosPerCell) / kNumberOfPhotosPerCell;
// The last cell can contain less than kNumberOfPhotosPerCell photos
NSUInteger thereIsALastCellWithLessThenFourPhotos = (photosCount % kNumberOfPhotosPerCell)?1:0;
// always add an extra cell
res = numberOfCompleteCell + thereIsALastCellWithLessThenFourPhotos +1 ;
} else if ( section > _lastLoadedPageIndex) {
// extra cell
res = 1;
} else res = kNumberOfRowsPerSection;
return res;
}
I'm the developer of GrabKit. Thanks for using it :)
Actually, the controllers in GrabKit are there for demonstration purpose only, they are not designed to be used "as is" in a project, and more specifically : The GRKDemoPhotosList controller was not made to have another controller pushed in the controllers hierarchy.
So what you need is just a little fix to make grabKit demo's controllers fit to your project :)
I guess the problem is the following :
_ in [GRKDemoPhotosList viewWillAppear], the method fillAlbumWithMorePhotos is called.
_ when you pop back from your controller to GRKDemoPhotosList, this method is called one more time, and it generates this bug.
Try to add a flag in the viewWillAppear method, to avoid calling fillAlbumWithMorePhotos twice, I guess it'll work fine.
Feel free to contact me by mail ( pierre.olivier.simonard at gmail.com ) or on twitter ( #pierrotsmnrd ) if you need more informations or help :)

Obj-C, how do I show `no rows found` in a UITableView and allow all rows to be deleted, getting error?

I'm getting the following error...
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 (1) 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, 1 deleted).
I've just added some code to show a row saying theres no data...
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
NSInteger num = [self.transactionsArray count];
if (num == 0) {
num = 1;
[dataTableView setEditing: FALSE animated: NO];
}
return num;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell1;
if(transactionsArray.count == 0) {
self.navigationItem.leftBarButtonItem.enabled = FALSE;
cell1 = [[[UITableViewCell alloc] initWithStyle:
UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
cell1.textLabel.text = #"No transactons found";
return cell1;
}
//Normal processing
I've stepped through before the error is produced and it occurs after numberOfRowsInSection is completed. So its finding that theres a section it wasn't expecting.
How do I work around this ?
EDIT:
Heres what happens just before the error
tableView:commitEditingStyle:forRowAtIndexPath:] [Line 630] commitEditingStyle start
tableView:commitEditingStyle:forRowAtIndexPath:] [Line 633] Delete now!
numberOfSectionsInTableView:] [Line 447] numberOfSectionsInTableView start
numberOfSectionsInTableView:] [Line 447] numberOfSectionsInTableView start
tableView:numberOfRowsInSection:] [Line 456] numberOfRowsInSection start
Do what the exception says. When you call -deleteRowsAtIndexPaths:withRowAnimation: to delete all rows in your table, insert one with -insertRowsAtIndexPaths:withRowAnimation: so that the number of rows is correct.