iOS Invalid Row Update Exception - ios

I'm making an SMS-style component of an iOS app. The messaging view is a UITableView. I want to add a new row to the table for both a sent message and an incoming message. The call to add a row for an incoming message comes through KVO via a dedicated serial thread. Similarly through KVO for send success, the call to add a row for a sent message comes from a different dedicated serial thread. If there is a call to add a row from an incoming message and another call to add a row for a sent message at near the exact same time, the app throws an exception for invalid rows:
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (31) must be equal to the number of rows contained in that section before the update (29), 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).'
Is this a thread safety issue? How can I fix it? Here is the relevant method called from the two different threads:
-(void)addRowToMessageTable {
dispatch_async(dispatch_get_main_queue(), ^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow: [_messageTable numberOfRowsInSection:0] inSection: 0];
[_messageTable beginUpdates];
[_messageTable insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[_messageTable endUpdates];
});
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _privateMessages.count;
}

Table view update method internally loads the table again so it again calls the delegate method . You are getting the exception because you have added the row to the table but you have not added that object to the _privateMessages array . Add same object to array so the count will be same before and after update .

Related

UITableView using deleteRowsAtIndexPaths but crashing app if new row created (vie a message being received)

in my app I have a UITableView containing messages. Each row is one message. When I delete a message, I first delete it from the messages array and then use deleteRowsAtIndexPaths:
int index = (int)[self.messages indexOfObject:message];
[self.messages removeObject:message];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];
This works fine however when a new message is received, the push notification triggers the table refresh. Sometimes a new message will be placed in the array just before deleteRowsAtIndexPaths finishes executing and hence the app crashes because the number of rows in the table after the method completes does not equal the number of rows before minus the number of deleted rows. Example error message:
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) and plus or minus the number of rows moved into or out of
that section (0 moved in, 0 moved out).'
Has anyone come across a problem like this before? Is there another approach or workaround I can take to stop the app crashing like this?
Any pointers would be great
I had the same kind of issue. And what i did:
declare activity flag for you update:
#property (nonatomic, assign) BOOL canEdit;
insert self.canEdit = NO when your perform some update operation
override willDisplayCell and check, if there will display last cell of your tableview's datasource array (so, update will end after that cell)
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([dataArray count]- 1 == indexPath.row && self.canEdit == NO)
self.canEdit = YES;}
When you receive push notification - check this flag, and if it's YES - perform update. If no - request update after some time

tableView editing delegate method not working(Invalid update: invalid number of rows in section 0)

'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (6) must be equal to the number of rows contained in that section before the update (6), 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).
->#property (nonatomic ,strong) NSMutableArray *name;
- (id)initWithFrame:(CGRect)frame viewName:(NSInteger)viewDecide datasourceName:(NSMutableArray *)nameRef
{
}
when i intialize the above MutableArray in this class
like
self.name=[NSMutableArray alloc]initWithObjects:#"1",#"2",#"3", nil];
then using table view editing delegate method , every thing works fine , when i click the button the table goes in edited mode with deletion and insertion.
but when i initialize the same array with data source which i passed in above method . the data inside the tableview render proprely but when i click the edit button its crash.
note:- i used
for row count-
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return[self.name count];
}
and for inserting
[strongSelf.tableViewDataView1 insertRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], nil] withRowAnimation:UITableViewRowAnimationLeft];
and for deleting i used
[strongSelf.tableViewDataView1 deleteRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], nil] withRowAnimation:UITableViewRowAnimationLeft];
:-strongSelf is used because this happening inside a block.
Your are removing the item from the table , not from your array. You have to remove the same from the array so that the count will remain same.

deleteRowsAtIndexPaths:withRowAnimation: not working

I'm trying to delete nicely the cells of mu UITableView :
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self deleteModelForIndexPath:indexPath];
[mainTableView beginUpdates];
[mainTableView deleteRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationTop];
[mainTableView endUpdates];
}
}
This throws the following exception :
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (3), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
I don't get the (0 inserted, 0 deleted) part. Why wouldn't it work ?
Thanks
You should update the table data at the same time. If you are using an array object for each row, remove the deleted one from the array.
The error means what it says:
The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (3), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
This is probably one of the better error messages out there and it basically means that the amount data backing your table view is out of sync with it's state in the UI, specifically the number of sections (hint: check –numberOfSectionsInTableView:) before/after your delete. If you delete a row/section from the table, you need to make sure you delete that row/section from the data backing the table.
(0 inserted, 0 deleted)
Means that the API didn't find that your data source had any deleted sections.
Are you absolutely certain that is doing what you think it is doing:
[self deleteModelForIndexPath:indexPath];
You shouldn't use beginUpdates and endUpdates methods for such an operation. You should simply do:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self deleteModelForIndexPath:indexPath];
[mainTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
}
Also you can take a look at the documentation about beginUpdates:
Call this method if you want subsequent insertions, deletion, and
selection operations (for example, cellForRowAtIndexPath: and
indexPathsForVisibleRows) to be animated simultaneously. This group of
methods must conclude with an invocation of endUpdates. These method
pairs can be nested. If you do not make the insertion, deletion, and
selection calls inside this block, table attributes such as row count
might become invalid. You SHOULD NOT call reloadData within the group;
if you call this method within the group, you will need to perform any
animations yourself.

UICollectionView: How to handle updates both before and after view is loaded?

I'm having issues updating a collection view when receiving those updates via KVO.
e.g. for deletion I perform the following:
dispatch_async(dispatch_get_main_queue(), ^{
NSUInteger index = [self.albumObjects indexOfObject:oldObject];
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:index inSection:0];
[self.dataSource removeObjectAtIndex:index];
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
}
The above gives me the error:
attempt to delete item 0 from section 0 which only contains 0 items before the update
Ok... so lets remove from the data source afterwards then which then returns the following error:
dispatch_async(dispatch_get_main_queue(), ^{
NSUInteger index = [self.albumObjects indexOfObject:oldObject];
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:index inSection:0];
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[self.dataSource removeObjectAtIndex:index];
}
However I now get the following error instead:
Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).
So it seems I can't win? What am I doing wrong here?
It seems the dispatch_async() causes the exception: when you modify your datasource (which adds your updater block to the end of the main queue), the item is already deleted when you try to update your collectionView.
What dataSource do you use? Is it managed manually? Or the the viewController is observing the datasource?

Add row to UITableView crash if initially empty table

I'm using the code below to refresh a tableview which I just added a row into. The code works for adding a row to the table if there is at least 1 row already in the table. However, it crashes if it was initially an empty table.
NSArray *indexPaths = [NSArray arrayWithObjects: [NSIndexPath indexPathForRow:[commentsArray count] inSection:0], nil];
[commentsTableView beginUpdates];
[commentsArray addObject:newestEntry];
[commentsTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationBottom];
[commentsTableView endUpdates];
[commentsTableView scrollToBottom:YES];
The crash response I get is:
*** 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 (0) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted).'
Can someone help me?
Well, the error is telling you the when UIKit calls tableView:numberOfRowsInSection is returning 0 both before and after your updates. So either you have an error in that method or in your update. You said in comment to a previous answer that your tableView:numberOfRowsInSection is correct, then it must be the update. If commentsArray is nil in the first update for some reason that might explain things. Perhaps try the following:
NSAssert(commentsArray, #"commentsArray is nil");
[commentsArray addObject:newestEntry];
The assert will be ignored in a release build, so use a debug build.
Just make sure the array which you are using to update is the same as Array which you are using while reloading the Table.
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [commentsArray count];
}
If this couldn't solve your problem, please put some code over here.

Resources