UITableView:用animation删除部分

更新

我已经发布了我的解决scheme,作为下面的答案这个问题。 从我的第一个修订版本采取不同的方法。


原来的问题我以前问过一个关于我认为解决了我的问题的问题:

在删除行时如何处理不可见的行。 (UITableViews)

不过,从UITableView中删除部分时,我现在又遇到了类似的问题。 (当我改变表中的部分/行数时,它们就重新出现了)。

因为我的文章的剪切长度,我失去了你之前,让我清楚地说明问题,你可以阅读尽可能多的,你需要提供一个答案。


问题:

如果批量删除UITableView中的行和部分,则应用程序有时会崩溃。 这取决于表的configuration以及我select删除的行和部分的组合。

日志说我崩溃了,因为它说我没有正确更新数据源和表:

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). 

现在很快,在你写出明显的答案之前,我向你保证,我已经确实从dataSource中正确地添加和删除了行和部分。 解释是漫长的,但你会发现下面的方法。

所以呢,如果你还有兴趣…


处理删除部分和行的方法:

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

我猜:

这个问题似乎是这样的:如果你从上面的列表中看到每个部分的单元格数量,你会看到第5部分看起来增加了1个。但是,这是不正确的。 原来的第5条实际上已经被删除,而另一个条款已经取代了它(具体地说,它是旧的第10条)。

那么为什么表格视图似乎没有意识到这一点? 它应该知道我删除了旧的部分不应该指望现在位于旧部分索引的新部分被删除部分的行数所约束。

希望这是有道理的,把这个写出来有点复杂。

(注意这个代码之前工作了不同数量的行/部分,这个特殊的configuration似乎给它的问题)

我之前遇到过这个问题。 您正尝试删除某个部分的所有行,然后再删除该部分。 但是,仅删除该部分是足够的(和适当的)。 其中的所有行也将被删除。 以下是我的项目中处理删除一行的示例代码。 它需要确定是否应该从一个部分中只删除这一行,或者如果它是该部分中剩余的最后一行,则删除整个部分:

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

我注意到你正在删除表中的部分,然后删除行。

我知道在Table View Programming Guide中有一个关于UITableViews 批量插入和删除的复杂讨论 ,但并没有特别的说明。

我认为发生的事情是,删除部分是导致行删除引用错误的行。

即你想从#4节删除节#2和#1 …但是在删除节#2之后,旧节#4现在是第三节,所以当你用旧的NSIndexPath删除(4,1)你正在删除一些可能不存在的随机不同的行。

所以我认为修复可能就像交换这两行代码一样简单,所以你首先删除行,然后删除这些部分。

所以最后这里是我对这个问题的解决scheme。 这种方法可以应用于任何大小的表格,任何数量的部分(据我所知)

和以前一样,我修改了Matt Gallagher的tableview Code,它把单元格特定的逻辑放在一个单独的单元格控制器中。 但是,您可以轻松地将此方法适用于不同的模型

我在Matt的代码中添加了以下(相关的)ivars:

 NSArray *allTableGroups; //always has a copy of every cell controller, even if filtered NSArray *filteredTableGroups; //always has a copy of the filtered table groups 

马特的原始伊娃:

 NSArray *allTableGroups 

总是指向上面的一个数组。

这可能会被重构和显着改善,但我没有这个需要。 另外,如果你使用核心数据,NSFetchedResultsController使这更容易。

现在到了这个方法(我正在尽可能地发表评论):

 - (void)createFilteredTableGroups{ //Checking for the usual suspects. all which may through an exception if(model==nil) return; if(tableGroups==nil) return; if([tableGroups count]==0) return; //lets make a new array to work with NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease]; //telling the table what we are about to do [self.tableView beginUpdates]; //array to track cells for deletion animation NSMutableArray *indexesToRemove = [NSMutableArray array]; //loop through each section for(NSMutableArray *eachSection in tableGroups){ //keeping track of the indexes to delete for each section NSMutableIndexSet *indexesForSection = [NSMutableIndexSet indexSet]; [indexesForSection removeAllIndexes]; //increment though cell indexes int rowIndex = 0; //loop through each cellController in the section for(ScheduleCellController *eachCellController in eachSection){ //Ah ha! A little magic. the cell controller must know if it should be displayed. //This you must calculate in your business logic if(![eachCellController shouldDisplay]){ //add non-displayed cell indexes [indexesForSection addIndex:rowIndex]; } rowIndex++; } //adding each array of section indexes, EVEN if it is empty (no indexes to delete) [indexesToRemove addObject:indexesForSection]; } //Now we remove cell controllers in newTableGroups and cells from the table //Also, each subarray of newTableGroups is mutable as well if([indexesToRemove count]>0){ int sectionIndex = 0; for(NSMutableIndexSet *eachSectionIndexes in indexesToRemove){ //Now you know why we stuck the indexes into individual arrays, easy array method [[newTableGroups objectAtIndex:sectionIndex] removeObjectsAtIndexes:eachSectionIndexes]; //tracking which cell indexPaths to remove for each section NSMutableArray *indexPathsToRemove = [NSMutableArray array]; int numberOfIndexes = [eachSectionIndexes count]; //create array of indexPaths to remove NSUInteger index = [eachSectionIndexes firstIndex]; for(int i = 0; i< numberOfIndexes; i++){ NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:sectionIndex]; [indexPathsToRemove addObject:indexPath]; index = [eachSectionIndexes indexGreaterThanIndex:index]; } //delete the rows for this section [self.tableView deleteRowsAtIndexPaths:indexPathsToRemove withRowAnimation:UITableViewRowAnimationTop]; //next section please sectionIndex++; } } //now we figure out if we need to remove any sections NSMutableIndexSet *sectionsToRemove = [NSMutableIndexSet indexSet]; [sectionsToRemove removeAllIndexes]; int sectionsIndex = 0; for(NSArray *eachSection in newTableGroups){ //checking for empty sections if([eachSection count]==0) [sectionsToRemove addIndex:sectionsIndex]; sectionsIndex++; } //updating the table groups [newTableGroups removeObjectsAtIndexes:sectionsToRemove]; //removing the empty sections [self.tableView deleteSections:sectionsToRemove withRowAnimation:UITableViewRowAnimationTop]; //updating filteredTableGroups to the newTableGroups we just created self.filteredTableGroups = newTableGroups; //pointing tableGroups at the filteredGroups tableGroups = filteredTableGroups; //invokes the animation [self.tableView endUpdates]; } 

我怀疑你忘记从内部存储中删除表示该部分的对象,因此-numberOfSectionsInTableView:方法在所有部分被删除后仍然返回1。

当我碰到同样的事故时,我就是这么做的。

我看到过早释放我的自定义tableview单元格的背景视图相同的确切的错误。

有了NSZombieEnabled,我得到一个exception被抛出一个函数的内部调用,以准备单元格重用。 没有NSZombieEnabled,我得到内部一致性错误。

顺便说一句,当我修复单元格的背景视图上的保留/释放问题时,我能够删除该部分的最后一行,而不必显式删除该部分。

道德故事:这个错误只是意味着当你尝试删除时发生了一些不好的事情,而当你删除时发生的事情之一就是这个单元格准备重用,所以如果你对你的tableview单元格做了任何自定义的事情,那里有可能的错误。

解决这个问题的简单方法是更新数据源,然后调用reloadSections

 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; 

这将重新加载一个部分。 或者,您可以使用indexSetWithIndexesInRange:同时重新加载多个部分。

或者只是做这个

 - (void)tableView:(UITableView *)tv commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if(editingStyle == UITableViewCellEditingStyleDelete) { //Delete the object from the table. [directoriesOfFolder removeObjectAtIndex:indexPath.row]; [tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } 

文件夹的目录是你的arrays! 那是所有上面的代码不适合我! 这样做更便宜,只是有道理!