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

Обновить

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


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

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

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

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


Проблема:

Если пакет удаляет строки AND из 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 в Руководстве по программированию таблиц, но это конкретно не касается этого.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 - (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 все выше коды не работали для меня! Это дешевле делать и имеет смысл!

  • Трюки для улучшения производительности прокрутки iPhone UITableView?
  • UISwitch в ячейке UITableView
  • Данные перезагрузки UITableView
  • Добавить салфетки для удаления UITableViewCell
  • 2 различных типа пользовательских UITableViewCells в UITableView
  • Прокрутка UITableView внутри UIScrollView
  • Обнаружение, которое UIButton было нажато в UITableView
  • UITableview с более чем одной пользовательской ячейкой с Swift
  • Как сделать UITableViewCell с UITextView внутри, который динамически регулирует его высоту на основе UITextView?
  • Что такое для?
  • Проблема UITableView при использовании отдельного делегата / источника данных
  • Давайте будем гением компьютера.