繁体   English   中英

UITableView:删除带动画的部分

[英]UITableView: deleting sections with animation

更新

我在下面的答案中发布了我的解决方案。 我的第一次修订采用了不同的方法。


原始问题我之前问了一个关于SO的问题我认为我解决了我的问题:

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

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

在我因为我的帖子的剪切长度而失去你之前,让我清楚地说明问题,你可以尽可能多地阅读以提供答案。


问题:

如果从UITableView批量删除行和部分,应用程序有时会崩溃。 这取决于表的配置以及我选择删除的行和部分的组合。

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

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部分)。

那么为什么表格视图似乎没有意识到这一点呢? 它应该知道我删除了旧的部分, 并且不应期望现在位于旧部分索引的新部分受删除部分的行数限制。

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

(请注意,此代码之前使用了不同数量的行/部分。这种特殊配置似乎给它带来了问题)

我之前遇到过这个问题。 您试图从一个部分删除所有行,然后,此外,现在是空的部分。 但是,仅删除该部分就足够了(并且适当)。 其中的所有行也将被删除。 以下是我的项目中的一些示例代码,用于处理删除一行。 它需要确定是否应该从一个部分中删除该行,或者删除整个部分(如果它是该部分中的最后一行):

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

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

我知道在“表格视图编程指南”中对UITableViews 的批量插入和删除有一个复杂的讨论 ,但它没有具体涉及这一点。

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

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

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

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

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

我已将以下(相关)ivars添加到Matt的代码中:

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

...始终指向上述阵列之一。

这可能会被重构和改进,但我没有必要。 此外,如果您使用Core Data,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。

当我遇到同样的崩溃时,这正是我做错了!

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

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

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

我看到了同样的错误,因为过早地释放了我的自定义tableview单元格的背景视图。

使用NSZombieEnabled,我得到了一个异常,它被抛到一个函数的内部调用之下,以便为重用单元做准备。 没有NSZombieEnabled,我收到了内部一致性错误。

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

故事的道德:这个错误只是意味着当您尝试删除时发生了一些不好的事情,并且当您删除时发生的事情之一就是细胞准备重用,所以如果您正在使用您的tableview单元格做任何自定义,请查看可能的错误。

或者只是这样做

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

文件夹的目录是你的数组! 多数民众赞成以上代码对我没有用! 这样做的成本更低,而且才有意义!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM