Crash when deleting row - Inconsistency in section not being updated - iphone

When I delete a row from UITableView using commitEditingStyle, my app crashes with the following error message. The odd thing though is I am deleting from section 3. The inconsistency according to the message is from section 4.
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1262.60.3/UITableView.m:920
2010-11-22 19:56:44.789 bCab[23049:207] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 4. 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 (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted).'
In datasource, I update section 4 depending on the number of rows in section 3. When a row is deleted from section 3, number of rows in section 4 goes from 0 to 1. This seems to cause the issue. Is there no way to avoid this?
Any pointers would be highly appreciated.
Thanks.
UPDATE:
numberOfSectionsInTableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 6;
}
numberOfRowsInSection
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
bCabAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
if (section == 0) { // First name, last name
return 2;
}
else if (section == 1) { // Password
return 2;
}
else if (section == 2) { // Mobile, DOB , Gender
return 3;
}
else if (section == 3) { // credit cards
return [creditCards count];
}
else if (section == 4) { // Add credit card
if ([creditCards count] >= 3) {
return 0;
}
else {
return 1;
}
}
else if (section == 5) {
return 0;
}
return 0;
}
commitEditingStyle
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
if (indexPath.section == 3) {
// Delete the row from the data source
NSLog(#"%d %d", indexPath.section, indexPath.row);
[creditCards removeObjectAtIndex:indexPath.row];
// Delete from backend
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
//[tableView reloadData];
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
I have tried with and without [tableView reloadData] with same results.
Thanks for trying to help!

In datasource, I update section 4
depending on the number of rows in
section 3. When a row is deleted from
section 3, number of rows in section 4
goes from 0 to 1. This seems to cause
the issue. Is there no way to avoid
this?
When using deleteRowsAtIndexPaths:withAnimation you are guaranteeing that the data source will have removed just the rows with the specified index paths. In your case you are also inserting a row into the table which means that state of the data source is not what the table view expects.
When deleting a row in section 3 that also involves inserting a row in section 4 you must do something like:
[self.tableView beginUpdates];
[self.tableView [NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPath:[NSArray arrayWithObject:indexPathForInsertedRow] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];

Before deleting the cell, do you remove the corresponding object in creditCards ?

How do you add the new row in section 4 when a row in section 3 is deleted ?
It is not a good solution, but you can try to make a reloadData when row is deleted.

Related

Crash When Swiping To Delete

I have several UITableViews in an app, and swipe to delete is working fine on all of them. The problem is, when I try to swipe over an empty cell (at the bottom), the app just crashes with:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-1914.84/UITableView.m:833
2012-03-24 16:20:03.158 [22339:707] Exception - attempt to delete row 3 from section 0 which only contains 3 rows before the update - attempt to delete row 3 from section 0 which only contains 3 rows before the update
Neither cellForRowAtIndexPath, commitEditingStyle nor editingStyleForRowAtIndexPath are called before the crash, its like the crash happens before any of my methods have chance to be called.
For reference, I have this in editingStyleForRowAtIndexPath
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if ((indexPath.row == self.insertIndex && indexPath.section == [self.sections count] -1) || (indexPath.row == 0 && [sections count]==0)) { // last row of section, or only row of only section
return UITableViewCellEditingStyleInsert;
} else {
return UITableViewCellEditingStyleDelete;
}
}
UPDATE: This is actually a huge problem, as the app is virtually unusable when the tableview scrolls.
It looks like the root cause of this was trying to reload rows in the table view while it was refreshing. There must have been some kind of inconsistent state which resulted, crashing the app every time.
solution:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{
// tableView.isEditing ? NSLog(#"show edit controls"):NSLog(#"don't show edit controls");
if(tableView.isEditing){
return NO;
}else{
return YES;
}
}

TableView Cell appearance issue

I have a tableVIew, where each cell is 90.0f tall (the height). my code;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 90.0;
}
Now when i delete all the records in the table. the height of the cell shrinks to its default value which is 44 (i think).
1.) My problem is that, i need the height of the cell to remain as 90.0f even after the user deletes all the records. How can i do this programatically ?
2.) I have given 2 colours to my cell.
cell.contentView.backgroundColor = indexPath.row % 2 ? [UIColor whiteColor] : [UIColor blackColor] ;
When i delete the second cell, which is black, then both the 1st and 3rd cell is white. How can i re-arrange this to be black and white in an order.
EDIT:
Ok this works but when i try to delete the last record in the table and [tableview indexPathsForVisibleRows] i end up getting Assertion failure in
-[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableView.m:961 and
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. Attempt to delete more rows than exist in section.'
Why is this ?
CODE:
[self.tableView beginUpdates];
[self.myArray removeObjectsInArray:discardedItems ];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:YES];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:YES];
[self.tableView endUpdates];
1) I don't think you can do that. One way around it would be to show x amount of blank cells to fill the page.
2) Call [tableView reloadData] upon cell deletion, or call [tableView reloadCellAtIndex...] on the rows before and after the deleted cell.
If the height of your rows is really static (like you showed), consider not implementing tableView:heightForRowAtIndexPath: but use the table-view's rowHeight property, instead.
This can be set either in code or in Interface Builder and should be the solution to your "the rows are shrinking if there are none" problem.
To your second problem:
Do you know about -[UITableView indexPathsForVisibleRows]?
You can use this to only reload the part of your table-view that needs to be updated. It would go like this:
- tableView:(UITableView *)table commitEditingStyle:(UITableViewCellEditingStyle)style forRowAtIndexPath:(NSIndexPath *)path
{
NSArray *visibleCellPaths = [table indexPathsForVisibleRows];
// replace this comment with whatever else needs to be done in any case...
if (style == UITableViewCellEditingStyleDelete) {
// replace this comment with the deletions of the model object
NSUInteger affectedPathIndex = [visibleCellPaths indexOfObject:path];
NSMakeRange updateRange = NSMakeRange(affectedPathIndex, [visibleCellPaths count] - affectedPathIndex);
NSArray *cellPathsToReload = [visibleCellPaths subarrayWithRange:updateRange];
// replace this comment with the bounds-checking.
// e.g. if you only had one section and the backing store was an array, it may look like this:
// while ([self.backingStore count] <= [[cellPathsToReload lastObject] row]) {
// NSUInteger remainingPathCount = [cellPathsToReload count];
// if (remainingPathCount) break;
// cellPathsToReload = [cellPathsToReload subarrayWithRange:NSMakeRange(0, remainingPathCount - 1)];
// }
[table reloadRowsAtIndexPaths:cellPathsToReload withRowAnimation:UITableViewRowAnimationFade];
}
}
set the rowHeight property of UITableView.
documentation states:
The height of each row (table cell) in the receiver.
please provide you data source code

UITableView - Remove last cell when there are a certain amount of cells above it

In my UITableView, I have a dedicated cell with an insert control at the bottom to allow the user to insert new rows.
What I want to do is remove/hide this cell when there are a certain number of cells above it (8 in this case).
Here's what I have so far:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 1) {
if ([sites count] == [[BrowserController sharedBrowserController] maximumTabs]) {
return [sites count];
} else {
return [sites count] + 1;
}
} else {
return 1;
}
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
...
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
NSString *newSiteAddress = [NSString stringWithString:#"http://"];
[sites addObject:newSiteAddress];
if ([sites count] == [[%c(BrowserController) sharedBrowserController] maximumTabs]) {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
[[(BookmarkTextEntryTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] textField] becomeFirstResponder];
}
}
Which causes the following exception to be thrown:
2/01/12 4:28:07.956 PM MobileSafari: *** 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 (8) must be equal to the number of rows contained in that section before the update (8), 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).'
*** First throw call stack:
(0x2bc0052 0x2d51d0a 0x2b68a78 0x1cf2db 0x747518 0x75282a 0x7528a5 0x812481c 0x75e7bb 0x8b2d30 0x2bc1ec9 0x6c65c2 0x6c655a 0x76bb76 0x76c03f 0x76b2fe 0x984a2a 0x2b949ce 0x2b2b670 0x2af74f6 0x2af6db4 0x2af6ccb 0x491879 0x49193e 0x6c3a9b 0x4430 0x2db5)
Here's a relevant paragraph from Apple's UITableView documentation which has to do with inserting and deleting cells:
Clicking on the insertion or deletion control causes the data source
to receive a tableView:commitEditingStyle:forRowAtIndexPath: message.
You commit a deletion or insertion by calling
deleteRowsAtIndexPaths:withRowAnimation: or
insertRowsAtIndexPaths:withRowAnimation:, as appropriate. Also in
editing mode, if a table-view cell has its showsReorderControl
property set to YES, the data source receives a
tableView:moveRowAtIndexPath:toIndexPath: message. The data source can
selectively remove the reordering control for cells by implementing
tableView:canMoveRowAtIndexPath:.
You should create new methods called "insertLastCell" and "deleteLastCell" (or something along those lines) which explicitly tells your table view to insert and delete that last cell via insertRowsAtIndexPaths:withRowAnimation: and deleteRowsAtIndexPaths:withRowAnimation:.
Once the insert and deletes are "committed", only then can you report back different numbers in your numberOfRowsInSection method.

UITableView Section Header issue

All,
I have a grouped UITableView with a possible total of 3 sections. There could be 1, 2 or 3.
My issue is that for each section I use a different header & footer view. I am choosing which header/footer to show by checking the section #.
This obviously does not work, as section 0 does not always represent what 'header' 0 shows.
Example:
Header #0 = "Game in progress". But no games in progress are returned from the database. Only 'Games Ended" exist. Therefore section 0 would be all 'games ended'. I don't want 'Games Ended' to use the 'Games in Progress' header.
I can't find a way to check the section value, and not the number.
To put it simply, I would like to be able to show section header #3 for section name #3, even if section name #3 is section #0.
I know this seems trivial, and is probably simple... but I am stuck. Any help is appreciated.
Thanks.
----- CODE -----
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[fetchedResultsController_ sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController_ sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if(section == 0)
{
return 50.0f;
}
else if (section == 1)
return 50.0f;
else
return 50.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
if(section == 0 )
{
return 50.0f;
}
else if (section == 1)
return 5.0f;
else
return 80.0f;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if(section == 0)
{
return headerView1;
}
else if (section == 1)
return headerView2;
else
return headerView3;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
if(section == 0)
{
return footerView1;
}
else if (section == 1)
return footerView2;
else
return footerView3;
}
Obviously, deciding which header / footer to show by checking the section # is wrong (this is bad MVC). For a solution to your problem, it would be better to see some actual code, although I think I can suggest something in general:
The sections that you show are taken out of some data source - an array, a dictionary or some other collection (this is the same collection you use to determine, for example, the return value for the numberOfSectionsInTableView: delegate method. If you haven't done so already, you should incorporate these data instances into some object that contains the data itself (this is the data that you normally need for displaying the cell/header/footer elements along with the actual data values) - In this object, add an additional "HeaderType" enumerated value, so that each object "knows" how it is supposed to be displayed. This way your MVC is perfect: You have your data stored in a collection of custom objects, your controller knows how to display the data by it's type and of course your view shows the data properly based on the controller's instructions.
Here is an example of an enumeration that could help:
typedef enum {
kHeaderTypeGameProgress,
kHeaderTypeGameStats,
kHeaderTypeGameDate
} HeaderType;
In your "viewForHeader" or "viewForFooter" methods, just add a switch type to check the data's HeaderType and create a view accordingly. Hope I helped, good luck!
It seems that in your cellForRowAtIndexPath, you must already have some logic that decides what group to show data from, maybe something like:
NSArray *group;
int section = indexPath.section;
if (![gamesInProgress count]) section++;
switch (section) {
case 0:
group = gamesInProgress;
break;
case 1:
group = finishedGames;
break;
// etc.
}
In your viewForHeaderInSection, write similar code that sets a NSString instead of NSArray.

UITableView Deleteing row error

I am a newbie to iPhone app development. So please go easy with me :)
I was trying to implement delete row from UItableView when i get this error, which i am not able understand why
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 (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).'
Here is my code to delete item method
-(void)deleteItem:(NSIndexPath *)path{
Item *i = (Item *)[list objectAtIndex:path.row];
NSLog(#"Deleting item [%#]", i.iName);
int ret;
const char *sql = "delete from items where id = ?;";
if (!deleteStmt)
{ // build update statement
if ((ret=sqlite3_prepare_v2(db, sql, -1, &deleteStmt, NULL))!=SQLITE_OK)
{
NSAssert1(0, #"Error building statement to delete items [%s]", sqlite3_errmsg(db));
}
}
// bind values to statement
NSInteger n = i.iId;
sqlite3_bind_int(deleteStmt, 1, n);
// now execute sql statement
if ((ret=sqlite3_step(deleteStmt)) != SQLITE_DONE)
{
NSAssert1(0, #"Error deleting item [%s]", sqlite3_errmsg(db));
}
// now reset bound statement to original state
sqlite3_reset(deleteStmt);
[list removeObjectAtIndex:path.row]; // remove from table
[self readTable]; // refresh array
}
and this is the commitediting style of the UITableView
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[appDelegate deleteItem:indexPath];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
Can someone please tell me what i am doing wrong. From what i understand the number of rows in the section is not updated. Am i right ??
Thanks in advance.
As stated in the Apple's Table View Programming Guide for iOS the deletion order is relevant:
first delete the item from the table;
second delete it from the model.
So you have to switch the two delete commands:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[appDelegate deleteItem:indexPath];
In my modest opinion and after trying to the same thing, looks like the table still believes the number of rows didn't change, so I make the row hidden and looks like it worked for me.
After some re-looking at the code, figured out the solution myself. The problem was with the the
readTable()
where the older array was not emptied before reading it again. I used the
[list removeAllObjects]
method to empty it. It worked finally. :)
I don't see a [table reloadData]; does it work? Usually it is required.