Подтвердить что ты не робот

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

Обновление

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


Оригинальный вопрос Я ранее задавал вопрос о том, что я решил решить свои проблемы:

Как работать с невидимыми строками во время удаления строки. (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 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).

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Я добавил следующие (релевантные) 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

Матовый оригинал ivar:

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


}

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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