TableView: cellForRowAtIndexPath вызывается с nil indexPath после удаления элемента

У меня есть довольно vanilla UITableView, которым управляет NSFetchedResultsController, для отображения всех экземпляров данного объекта Core Data.

Когда пользователь удаляет запись в виде таблицы, прокручивая ее, tableView:cellForRowAtIndexPath: в конечном итоге вызывается на мой UITableViewController с nil indexPath. Поскольку я не ожидал, что он будет вызван с помощью nil indexPath, приложение выйдет из строя.

Я могу обойти крушение, проверив это значение nil и вернув пустую ячейку. Кажется, что это работает, но я все еще беспокоюсь о том, что, возможно, я что-то неправильно сделал. Есть идеи? Кто-нибудь когда-либо видел tableView:cellForRowAtIndexPath: с nil indexPath?

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

Итак, действительно ли ситуация ОК, чтобы получить метод nil indexPath в методе делегата табличного представления?

Мой код контроллера просмотра действительно стандартный. Вот удаление:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source
        NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
        [self.moc deleteObject:managedObject];
        NSError *error = NULL;
        Boolean success = [self.moc save:&error];
        if (!success) { <snip> }
        // actual row deletion from table view will be handle from Fetched Result Controller delegate
        // [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    } else { <snip> }   
}

Это приведет к вызову метода делегата NSFetchedResultsController:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
   atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;
    switch(type) {

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

        case NSFetchedResultsChangeInsert: <snip> break;
        case NSFetchedResultsChangeUpdate: <snip> break;
        case NSFetchedResultsChangeMove: <snip> break;
    }
}

И, конечно, методы источника данных обрабатываются с помощью NSFetchedResultsController, например:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

Большое спасибо.

Ответы

Ответ 1

Кажется, что вы удаляете indexPath из таблицы, но источник данных таблицы не обновляется. Проверяли ли вы процесс инициализации источника данных NSFetchedResultsController корректно обновлял источник данных таблицы??

Ответ 2

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

#pragma mark -
#pragma mark Fetched results controller delegate


- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}


- (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)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
   atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath *)newIndexPath {

UITableView *tableViews = self.tableView;

switch(type) {

    case NSFetchedResultsChangeInsert:
        [tableViews insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeDelete:
        [tableViews deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeUpdate:
        [_delegate configureCell:[tableViews cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
        break;

    case NSFetchedResultsChangeMove:
        [tableViews deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [tableViews insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
        break;
}
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    [self.tableView scrollToRowAtIndexPath:indexPath
                      atScrollPosition:UITableViewScrollPositionMiddle
                              animated:NO];
}

Ответ 3

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

У меня есть метод FetchedResultsController Delegate в моем коде

     - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
  // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [self.myTableView beginUpdates];
 }


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
        [self.myTableView endUpdates];

}