Вопрос по iphone – UITableView: удаление разделов с анимацией

40

Update

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

Original Question Ранее я задавал вопрос о SO, который, как я думал, решил мои проблемы:

Как бороться с невидимыми строками во время удаления строк. (UITableViews)

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

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

Problem:

Если пакетное удаление строк и разделов из 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).

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

Так что, если вы все еще заинтересованы, & # x2026;

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:

Кажется, проблема заключается в следующем: если вы посмотрите выше, где я перечисляю количество ячеек в каждом разделе, вы увидите, что раздел 5 кажется увеличенным на 1. Однако это не так. Первоначальный раздел 5 был фактически удален, и его место занял другой раздел (в частности, это старый раздел 10).

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

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

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

Ваш Ответ

7   ответов
3

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

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

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

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

& # x2026; всегда указывает на один из приведенных выше массивов.

Это, вероятно, может быть подвергнуто рефакторингу и значительно улучшено, но у меня не было необходимости. Кроме того, если вы используете 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];


}
0

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

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

1

анных, а затем вызватьreloadSections

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

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

88

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

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

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

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

Цитирую вопрос: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.
4

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

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

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

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

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

Кроме того, отслеживайте indexPath для каждой ячейки, от которой нужно избавиться, и корректируйте их соответствующим образом по мере удаления. (Это может быть длинный / запутанный / неуместный способ сделать это - просто мысль.)
@ Тим интересная мысль. Вы правы, это может быть довольно утомительно с большим количеством удалений (которые у меня будут). Мне также интересно, могу ли я сделать несколько удалений в быстрой последовательности. Я пытался сделать пакетное удаление, чтобы избежать этих проблем, но это может быть необходимо. Corey Floyd
Я предполагаю, что следующим моим шагом будет попытка подумать о том, какие конфигурации таблиц вызывают сбои, а не нет. Кроме того, убедитесь, что отфильтрованная таблица показывает правильные строки, чтобы вы не удаляли то, чего не ожидали ... попробуйте несколько простых случаев, когда вы удаляете, скажем, один целый раздел и одну строку из раздела до или после него. и убедитесь, что строки, которые вы ожидали, ушли ...
Я делаю пакетное удаление, поэтому нет никакой разницы в порядке перечисления операций. Табличное представление выполняет операции "сразу" когда они находятся в блоке обновления. Поскольку я был параноиком, я попытался изменить порядок операций безрезультатно. Нумерация разделов / строк не переключается (не должна переключаться) во время удаления партии. Если бы вы не использовали блоки, вы были бы правы. Corey Floyd
Если вы посмотрите на мои NSLogs, я распечатал соответствующую информацию, показывающую, почему именно она выдает исключение. Я проверил другие случаи, которые работают, но этот конкретный случай ломается. Я пытаюсь выяснить, почему этот случай ломается, а другие нет. Corey Floyd
1

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

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

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

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

Похожие вопросы