Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView: deleting sections with animation

Update

I have posted my solution to this problem as an answer below. It takes a different approach from my first revision.


Original Question I previously asked a question on SO that I thought solved my issues:

How to deal with non-visible rows during row deletion. (UITableViews)

However, I now have similar problems again when removing sections from a UITableView. (they resurfaced when I varied the number of sections/rows in the table).

Before I lose you because of the shear length of my post, let me state the problem clearly, and you can read as much as you require to provide an answer.


Problem:

If batch deleting rows AND sections from a UITableView, the application crashes, sometimes. It depends on the configuration of the table and the combination of rows and sections I choose to remove.

The log says I crashed because it says I have not updated the datasource and the table properly:

Invalid update: invalid number of rows in section 5.  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). 

Now quickly, before you write the obvious answer, I assure you I have indeed added and deleted the rows and sections properly from the dataSource. The explanation is lengthy, but you will find it below, following the method.

So with that, if you are still interested…


Method that handles removal of sections and rows:

- (void)createFilteredTableGroups{      //index set to hold sections to remove for deletion animation     NSMutableIndexSet *sectionsToDelete = [NSMutableIndexSet indexSet];     [sectionsToDelete removeIndex:0];       //array to track cells for deletion animation     NSMutableArray *cellsToDelete = [NSMutableArray array];      //array to track controllers to delete from presentation model     NSMutableArray *controllersToDelete = [NSMutableArray array];      //for each section     for(NSUInteger i=0; i<[tableGroups count];i++){          NSMutableArray *section = [tableGroups objectAtIndex:i];          //controllers to remove         NSMutableIndexSet *controllersToDeleteInCurrentSection = [NSMutableIndexSet indexSet];         [controllersToDeleteInCurrentSection removeIndex:0];         NSUInteger indexOfController = 0;          //for each cell controller         for(ScheduleCellController *cellController in section){              //bool indicating whether the cell controller's cell should be removed             NSString *shouldDisplayString = (NSString*)[[cellController model] objectForKey:@"filteredDataSet"];             BOOL shouldDisplay = [shouldDisplayString boolValue];              //if it should be removed             if(!shouldDisplay){                  NSIndexPath *cellPath = [self indexPathOfCellWithCellController:cellController];                   //if cell is on screen, mark for animated deletion                 if(cellPath!=nil)                     [cellsToDelete addObject:cellPath];                  //marking controller for deleting from presentation model                 [controllersToDeleteInCurrentSection addIndex:indexOfController];                              }             indexOfController++;         }          //if removing all items in section, add section to removed in animation         if([controllersToDeleteInCurrentSection count]==[section count])             [sectionsToDelete addIndex:i];          [controllersToDelete addObject:controllersToDeleteInCurrentSection];      }       //copy the unfiltered data so we can remove the data that we want to filter out     NSMutableArray *newHeaders = [tableHeaders mutableCopy];     NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease];       //removing controllers     int i = 0;     for(NSMutableArray *section in newTableGroups){         NSIndexSet *indexesToDelete = [controllersToDelete objectAtIndex:i];         [section removeObjectsAtIndexes:indexesToDelete];         i++;     }      //removing empty sections and cooresponding headers     [newHeaders removeObjectsAtIndexes:sectionsToDelete];     [newTableGroups removeObjectsAtIndexes:sectionsToDelete];      //update headers     [tableHeaders release];     tableHeaders = newHeaders;      //storing filtered table groups     self.filteredTableGroups = newTableGroups;       //filtering animation and presentation model update     [self.tableView beginUpdates];     tableGroups = self.filteredTableGroups;     [self.tableView deleteSections:sectionsToDelete withRowAnimation:UITableViewRowAnimationTop];     [self.tableView deleteRowsAtIndexPaths:cellsToDelete withRowAnimation:UITableViewRowAnimationTop];     [self.tableView endUpdates];       //marking table as filtered     self.tableIsFiltered = YES;    } 

My guess:

The problem seems to be this: If you look above where I list the number of cells in each section, you will see that section 5 appears to increase by 1. However, this is not true. The original section 5 has actually been deleted and another section has taken its place (specifically, it is old section 10).

So why does the table view seem not to realize this? It should KNOW that I removed the old section and should not expect a new section that is now located at the old section's index to be bound by the deleted section's number of rows.

Hopefully this makes sense, it is a little complicate to write this out.

(note this code worked before with a different number of rows/sections. this particular configuration seems to give it issues)

like image 981
Corey Floyd Avatar asked Jun 29 '09 23:06

Corey Floyd


People also ask

How to delete a section in tableView?

The key for deleting cells and sections from table views is to first delete the data corresponding to those cells/sections from your data source, and then call the appropriate deletion method on the table view. After the deletion method finishes, the table view will refer back to its data source object.

How to remove cell from tableView?

So, to remove a cell from a table view you first remove it from your data source, then you call deleteRows(at:) on your table view, providing it with an array of index paths that should be zapped. You can create index paths yourself, you just need a section and row number.


1 Answers

I’ve run into this problem before. You are trying to delete all rows from a section and then, in addition, that now empty section. However, it is sufficient (and proper) to remove that section only. All rows within it will be removed as well. Here is some sample code from my project that handles deletion of one row. It needs to determine whether it should remove only this row from a section or delete the entire section if it is the last remaining row in that section:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {     if (editingStyle == UITableViewCellEditingStyleDelete)     {         // modelForSection is a custom model object that holds items for this section.         [modelForSection removeItem:[self itemForRowAtIndexPath:indexPath]];          [tableView beginUpdates];          // Either delete some rows within a section (leaving at least one) or the entire section.         if ([modelForSection.items count] > 0)         {             // Section is not yet empty, so delete only the current row.             [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]                              withRowAnimation:UITableViewRowAnimationFade];         }         else         {             // Section is now completely empty, so delete the entire section.             [tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]                       withRowAnimation:UITableViewRowAnimationFade];         }          [tableView endUpdates];     } } 
like image 84
Martin Winter Avatar answered Sep 23 '22 18:09

Martin Winter