简体   繁体   English

UITableView:删除带动画的部分

[英]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: 原始问题我之前问了一个关于SO的问题我认为我解决了我的问题:

How to deal with non-visible rows during row deletion. 如何在删除行时处理不可见的行。 (UITableViews) (UITableViews)

However, I now have similar problems again when removing sections from a UITableView. 但是,从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. 如果从UITableView批量删除行和部分,应用程序有时会崩溃。 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. 现在很快,在你写出明显的答案之前,我向你保证我确实已经从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. 问题似乎是这样的:如果你看一下我列出每个部分中单元格数量的位置,你会发现第5部分似乎增加了1.但是,这不是真的。 The original section 5 has actually been deleted and another section has taken its place (specifically, it is old section 10). 原来的第5部分实际上已被删除,另一部分取而代之(具体来说,它是旧的第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) (请注意,此代码之前使用了不同数量的行/部分。这种特殊配置似乎给它带来了问题)

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

I notice that you're deleting the sections from the table first, and then deleting rows. 我注意到你先删除表中的部分,然后删除行。

I know there's a complicated discussion of batch insertion and deletion for UITableViews in the Table View Programming Guide, but it doesn't specifically cover this. 我知道在“表格视图编程指南”中对UITableViews 的批量插入和删除有一个复杂的讨论 ,但它没有具体涉及这一点。

I think what's happening is that deleting the sections is causing the row deletions to refer to the wrong row. 我认为发生的事情是删除部分导致行删除引用错误的行。

ie you want to delete section #2 and row #1 from section #4... but after you've deleted section #2, the old section #4 is now the third section, so you when you delete with the old NSIndexPath of (4, 1) you're deleting some random different row that may not exist. 即你要删除第4节中的第2节和第1行......但是在删除第2节之后,旧节#4现在是第3节,所以当你用旧的NSIndexPath删除时(4,1)你正在删除一些可能不存在的随机不同的行。

So I think the fix might be as simple as swapping those two lines of code, so you're deleting the rows first, then the sections. 所以我认为修复可能就像交换这两行代码一样简单,所以你先删除行,然后删除各部分。

So finally here is my solution to this issue. 所以最后这是我对这个问题的解决方案。 This method can be applied to tables of any size, any number of sections (as far as I can tell) 这个方法可以应用于任何大小,任意数量的部分的表(据我所知)

As before I have modified Matt Gallagher's tableview Code which places cell-specific logic in a separate cell controller. 和以前一样,我修改了Matt Gallagher的tableview Code,它将特定于单元的逻辑放在一个单独的单元控制器中。 However, you can easily adapt this method to a different model 但是,您可以轻松地将此方法适用于不同的模型

I have added the following (relevant) ivars to Matt's 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

Matt's original ivar: 马特的原始伊娃:

NSArray *allTableGroups

…always points to one of the above arrays. ...始终指向上述阵列之一。

This can probably be refactored and improved significantly, but I haven't had the need. 这可能会被重构和改进,但我没有必要。 Also, if you use Core Data, NSFetchedResultsController makes this easier. 此外,如果您使用Core Data,NSFetchedResultsController使这更容易。

Now on to the method (I am trying to comment as much as I can): 现在开始使用该方法(我尽可能多地评论):

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


}

I suspect that you are forgetting to remove the object representing the section from your internal storage, so that the -numberOfSectionsInTableView: method is still returning 1 after all sections are deleted. 我怀疑您忘记从内部存储中删除表示该部分的对象,以便在删除所有部分后-numberOfSectionsInTableView:方法仍然返回1。

That's exactly what I was doing wrong when I had the same crash! 当我遇到同样的崩溃时,这正是我做错了!

A much simpler way to address this is to update your data source, then call reloadSections 解决此问题的一种更简单的方法是更新数据源,然后调用reloadSections

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

This will reload a single section. 这将重新加载一个部分。 Alternatively you could use indexSetWithIndexesInRange: to reload multiple sections simultaneously. 或者,您可以使用indexSetWithIndexesInRange:同时重新加载多个部分。

I saw this same exact error as the result of prematurely releasing the background view of my custom tableview cell. 我看到了同样的错误,因为过早地释放了我的自定义tableview单元格的背景视图。

With NSZombieEnabled I got a an exception being thrown way down below an internal call to a function to prepare the cell for reuse. 使用NSZombieEnabled,我得到了一个异常,它被抛到一个函数的内部调用之下,以便为重用单元做准备。 Without NSZombieEnabled, I was getting the Internal consistency error. 没有NSZombieEnabled,我收到了内部一致性错误。

Incidentally when I fixed the retain/release issue on the background view of the cell, I was able to delete the last row of the section without having to delete the section explicitly. 顺便说一下,当我在单元格的背景视图上修复了保留/释放问题时,我能够删除该部分的最后一行而不必显式删除该部分。

Moral of the story: This error just means something bad is happening when you try to delete, and one of the things that happens when you delete is the cell gets prepared for reuse, so if you are doing anything custom with your tableview cells, look for a possible error there. 故事的道德:这个错误只是意味着当您尝试删除时发生了一些不好的事情,并且当您删除时发生的事情之一就是细胞准备重用,所以如果您正在使用您的tableview单元格做任何自定义,请查看可能的错误。

or just do this 或者只是这样做

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

directories of folder being your Array! 文件夹的目录是你的数组! Thats is all above codes didnt work for me! 多数民众赞成以上代码对我没有用! This is less expensive to do and just makes sense! 这样做的成本更低,而且才有意义!

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

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