Сбой при UITableView endUpdates при перемещении последней строки в разделе
У меня есть UITableViewController, который поддерживается NSFetchedResultsController.
Мой NSFetchedResultsController помещает результаты в два раздела на основе логического.
В фоновом потоке источник данных изменяется таким образом, что строки добавляются или удаляются.
У меня есть фоновый поток NSManagedObjectContext, который сливается правильно, и эта ситуация работает нормально для большинства случаев. Строки изменяются при изменении данных и перемещаются между разделами с анимацией.
Однако существует одна ситуация, когда мое приложение вылетает с EXC_BAD_ACCESS.
Дело в том, что последняя строка в разделе перемещается в другой раздел. (След стека ниже). Сбой происходит в objc_msgSend, но обычные советы по отладке, которые я использую, не возвращает ничего полезного, я просто получаю: "Значение не может быть преобразовано в целое" от gdb
.
Методы NSFetchedResultsControllerDelegate вызывают в следующем порядке:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
// ^ The parameters specify a deletion of the 1st section
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath;
// ^ The parameters specify a moved row from the 1st section to the 1st section
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
Просматривая StackOverflow, многие из тех, кто сталкивается с подобными проблемами, поддерживают свой UITableViewController вручную, используя NSArray или NSMutableArray, и они просто не обновляют источник данных в нужное время. В этом случае NSFetchedResultsController обрабатывает возвращающее количество строк и разделов и определенно обновляется к тому времени, когда вызывается [tableView endUpdates]
.
Есть ли у кого-нибудь какие-либо советы по отладке, указатели или решения?
Это сообщение в блоге намекает на работу вокруг аналогичной проблемы, когда создается новый раздел.
У меня есть два варианта, если я не могу решить эту проблему, как есть:
Update
Я изменил образец приложения Core Data "Locations" Apple, чтобы напрямую использовать NSFetchedResultsController и воспроизвел проблему.
Я загрузил исходный код для этого проекта. Используйте этот проект, чтобы воспроизвести проблему.
- Просто нажмите кнопку + несколько раз и подождите
- Каждые 5 секунд элемент перемещается в другую категорию.
- Когда первая категория будет опустошена, она сработает.
Иногда возникает проблема создания двух анимаций для одной и той же ячейки в соответствии с другими вопросами StackOverflow по этой теме. Чаще всего авария обсуждается выше и ниже.
Ссылка
Трассировка стека:
#0 0x31e0afbc in objc_msgSend ()
#1 0x32c11522 in -[_UITableViewUpdateSupport(Private) _computeRowUpdates] ()
#2 0x32c10510 in -[_UITableViewUpdateSupport initWithTableView:updateItems:oldRowData:newRowData:oldRowRange:newRowRange:context:] ()
#3 0x32c0f99e in -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] ()
#4 0x32c0e66c in -[UITableView endUpdatesWithContext:] ()
#5 0x000088d6 in -[DraftHistoryController controllerDidChangeContent:] (self=0x3f6f1e0, _cmd=0x377ca29c, controller=0x3f716e0) at /Users/ataylor/Documents/Documents/Programming/iPhone/Drafter/Drafter/Drafter/DraftHistoryController.m:323
#6 0x3775a892 in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()
Мои методы NSFetchedResultsControllerDelegate те же, что и в примере с образцом Apple:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates]; // <---- Crash occurs here
}
Соответствующие методы UITableViewControllerDelegate следующие:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
Ответы
Ответ 1
Я быстро посмотрел. Проект измененных местоположений не разбился обо мне, но он действительно генерировал исключения основных данных. Проблема заключается в перезагрузке секций в контроллере: didChangeObject:. Я изменил код следующим образом, и все выглядит хорошо для меня (включая заголовки разделов) на iOS 4.3 и iOS 5.
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
// other cases here
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
Ответ 2
Мне было очень интересно узнать о коде, который исправил проблему с добавлением нового раздела, поэтому я решил поместить его вместо моих текущих методов NSFetchedResultsControllerDelegate, и он действительно решил мою проблему. Прослеживая, я обнаружил, что этот код из образца Apple "Locations" падает при использовании NSFetchedResultsController:
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
Но следующий код отлично работает:
case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationTop];
}
else {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
}
break;
У меня нет случая, когда заголовки разделов будут меняться, я не буду продолжать отладку, чтобы решить проблему. Код Apple предназначен для исправления заголовков разделов.
Ответ 3
Потому что каждый вопрос требует быстрого ответа, здесь один (на основе принятого XJones ответа). Как и в случае с другими ответами, магия находится в .Move: удалять и вставлять, а не перемещать строку.
Это мой делегат NSFRC didChangeObject:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Delete:
if let deletePath = indexPath {
self.tableView.deleteRowsAtIndexPaths([deletePath], withRowAnimation: .None)
}
break
case .Insert:
if let insertPath = newIndexPath {
self.tableView.insertRowsAtIndexPaths([insertPath], withRowAnimation: .Fade)
}
break
case .Update:
if let updatePath = indexPath {
self.tableView.reloadRowsAtIndexPaths([updatePath], withRowAnimation: .None)
}
break
case .Move:
if let indexPath = indexPath, newIndexPath = newIndexPath {
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
break
}
}
Ответ 4
Я получал сбой сверхурочного времени. Я удалил последнюю строку в разделе.
Убедитесь, что вы внедрили контроллер: didChangeSection: atIndex: forChangeType: для удаления раздела.
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.viewTable insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.viewTable deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}