Crash When Swiping To Delete - iphone

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;
}
}

Related

Source Index path of last row is being set to '0' when re-ordering row

Has anybody come across this situation?
When I am re-ordering the cells from the bottom most to top most I get a crash. These crashes happens when iSourceIndexPath.row becomes 0. I am wondering how is my iSourceIndexPath.row becoming 0 as this is the last row I am picking always. Any clue?
- (NSIndexPath *)tableView:(UITableView *)iTableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)iSourceIndexPath toProposedIndexPath:(NSIndexPath *)iProposedDestinationIndexPath {
NSIndexPath *aReturnIndexPath = iProposedDestinationIndexPath;
if(iProposedDestinationIndexPath.row == 0) {
aReturnIndexPath = iSourceIndexPath;
}
NSLog(#"iProposedDestinationIndexPath=%d iSourceIndexPath=%d aReturnIndexPath=%d",iProposedDestinationIndexPath.row,iSourceIndexPath.row,aReturnIndexPath.row);
return aReturnIndexPath;
}
I forgot to reload the table after cells are moved. You should do this apart from your regular handling of cells in it.
- (void)tableView:(UITableView *)iTableView moveRowAtIndexPath:(NSIndexPath *)iFromIndexPath toIndexPath:(NSIndexPath *)iToIndexPath {
[self.tableView reloadData]
}

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.

Crash when deleting row - Inconsistency in section not being updated

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.

Preventing cell reordering for particular cells in iPhone SDK

I have 2 sections in my UITableView. I want the 2nd section to be movable but the 1st section of cells not.
Specifying canEditRowAtIndexPath and canMoveRowAtIndexPath doesn't help - the first section cells although not showing drag controls, they still change places if a cell from the 2nd section is dragged over.
Is there a workaround for this?
Try implementing the targetIndexPathForMoveFromRowAtIndexPath method and forcing the row from the second section back to its original place if user tries to move it to the first section:
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if (proposedDestinationIndexPath.section == 0)
{
return sourceIndexPath;
}
else
{
return proposedDestinationIndexPath;
}
}