UITableView: удаление разделов с анимацией - PullRequest
40 голосов
/ 30 июня 2009

Обновление

Я отправил свое решение этой проблемы в качестве ответа ниже. Это отличается от моей первой ревизии.


Оригинальный вопрос Ранее я задавал вопрос о 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).

Теперь быстро, прежде чем написать очевидный ответ, уверяю вас, я действительно добавил и удалял строки и разделы должным образом из источника данных. Объяснение длинное, но вы найдете его ниже, следуя методу.

Итак, если вы все еще заинтересованы ...


Метод обработки удаления разделов и строк:

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

Так почему табличное представление не осознает этого? Следует знать, что я удалил старый раздел и , и не следует ожидать, что новый раздел, который теперь расположен по индексу старого раздела, будет связан с количеством строк в удаленном разделе.

Надеюсь, это имеет смысл, это немного сложно записать.

(обратите внимание, что этот код работал раньше с другим количеством строк / разделов. Эта конкретная конфигурация, похоже, создает проблемы)

Ответы [ 7 ]

88 голосов
/ 12 августа 2009

Я сталкивался с этой проблемой раньше. Вы пытаетесь удалить все строки из раздела, а затем, кроме того, этот пустой раздел. Однако достаточно (и правильно) удалить только этот раздел. Все строки в нем также будут удалены. Вот пример кода из моего проекта, который обрабатывает удаление одной строки. Необходимо определить, следует ли удалить только эту строку из раздела или удалить весь раздел, если это последняя оставшаяся строка в этом разделе:

- (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];
    }
}
4 голосов
/ 02 июля 2009

Я заметил, что вы сначала удаляете разделы из таблицы, а затем удаляете строки.

Я знаю, что сложное обсуждение пакетной вставки и удаления для UITableViews в Руководстве по программированию табличного представления, но это не касается конкретно этого.

Я думаю, что происходит то, что удаление разделов приводит к тому, что удаление строк ссылается на неправильную строку.

т.е. Вы хотите удалить раздел № 2 и строку № 1 из раздела № 4 ... но после того, как вы удалили раздел № 2, старый раздел № 4 теперь является третьим разделом, поэтому вы, когда вы удаляете со старым NSIndexPath из ( 4, 1) вы удаляете какую-то случайную другую строку, которая может не существовать.

Так что я думаю, что исправить это можно так же просто, как поменять местами две строки кода, поэтому сначала вы удаляете строки, а затем разделы.

3 голосов
/ 02 декабря 2009

Итак, вот мое решение этой проблемы. Этот метод может применяться к таблицам любого размера, любого количества секций (насколько я могу судить)

Как и прежде, я изменил код представления таблицы Мэтта Галлахера, который помещает специфичную для ячейки логику в отдельный контроллер ячейки. Тем не менее, вы можете легко адаптировать этот метод к другой модели

Я добавил следующие (соответствующие) ивары в код Мэтта:

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


}
1 голос
/ 09 июля 2014

Гораздо более простой способ решить эту проблему - обновить источник данных, а затем позвонить reloadSections

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

Это перезагрузит один раздел. В качестве альтернативы вы можете использовать indexSetWithIndexesInRange: для перезагрузки нескольких разделов одновременно.

1 голос
/ 09 мая 2012

Я подозреваю, что вы забыли удалить объект, представляющий раздел, из вашего внутреннего хранилища, так что метод -numberOfSectionsInTableView: по-прежнему возвращает 1 после удаления всех разделов.

Это именно то, что я делал неправильно, когда у меня был тот же сбой!

1 голос
/ 31 декабря 2010

Я видел ту же самую ошибку в результате преждевременного освобождения фонового представления моей пользовательской ячейки табличного представления.

С NSZombieEnabled я получил исключение, которое было сброшено ниже внутреннего вызова функции, чтобы подготовить ячейку для повторного использования. Без NSZombieEnabled я получил внутреннюю ошибку согласованности.

Между прочим, когда я исправил проблему сохранения / выпуска в фоновом представлении ячейки, я смог удалить последнюю строку раздела без явного удаления раздела.

Мораль истории: эта ошибка просто означает, что при попытке удаления происходит что-то плохое, и одна из вещей, которая происходит при удалении, - это то, что ячейка готовится к повторному использованию, поэтому, если вы делаете что-то свое с таблицей ячейки, поищите там возможную ошибку.

0 голосов
/ 18 сентября 2011

или просто сделай это

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

каталоги папок, являющихся вашим массивом! Вот и все вышеперечисленные коды не работают для меня! Это дешевле и имеет смысл!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...