UITableView, управляемый двумя NSFetchedResultsControllers с общими результатами - PullRequest
1 голос
/ 28 марта 2012

Я реализовал UITableViewController с UITableView, управляемым результатами двух NSFetchedResultsControllers (таблица с двумя разделами, один раздел для каждого NSFetchedResultsControllers), как ранее обсуждалось здесь .

Myкод выполняется правильно и без ошибок, если оба NSFetchedResultsControllers не имеют общих общих результатов.

Однако я сталкиваюсь с этой проблемой, когда вставляю объект Core Data, который появляется в результате в обоих моих NSFetchedResultsControllers:

Serious application error.  An exception was caught from the delegate of
NSFetchedResultsController during a call to -controllerDidChangeContent:.
Invalid update: invalid number of rows in section 0.  The number of rows contained in
an existing section after the update (1) 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 (1 inserted, 0 deleted). with userInfo (null)

Я выводлю каждый раз, когда меня спрашивают о количестве строк в разделе:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo;
    if (section < [self.fetchedResultsController1.sections count]) {
        sectionInfo = [[self.fetchedResultsController1 sections] objectAtIndex:section];
    }
    else {
        sectionInfo = [[self.fetchedResultsController2 sections] objectAtIndex:section-[self.fetchedResultsController1.sections count]];
    }

    NSLog(@"Section %d with %d row(s)", section, [sectionInfo numberOfObjects]);
    return [sectionInfo numberOfObjects];
}

, и распечатываю всякий раз, когда объект вставляется в didObject:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            NSLog(@"Inserting %@ %@", newIndexPath, anObject);
            [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;
    }
}

Когда я запускаю с вышеприведенным кодом, я получаю следующий вывод (сокращенно для ясности), с указанной выше ошибкой сразу следует следующее:

2012-03-28 00:59:55.611 MyApp[517:207] Section 1 with 0 row(s)
2012-03-28 00:59:55.612 MyApp[517:207] Section 0 with 0 row(s)
2012-03-28 00:59:55.613 MyApp[517:207] Inserting <NSIndexPath 0x5948150> 2 indexes [0, 0] <MyClass: 0xd60f8b0> (entity: MyClass; id: 0xd611d50 <x-coredata:///MyClass/tD7E0DB3C-4D85-468C-9DD0-BAD0A53B20A310> ; data: {
    ....
})
2012-03-28 00:59:55.614 MyApp[517:207] Section 0 with 1 row(s)
2012-03-28 00:59:55.614 MyApp[517:207] Section 1 with 0 row(s)
2012-03-28 00:59:55.618 MyApp[517:207] Inserting <NSIndexPath 0x5948150> 2 indexes [0, 0] <MyClass: 0xd60f8b0> (entity: MyClass; id: 0xd611d50 <x-coredata:///MyClass/tD7E0DB3C-4D85-468C-9DD0-BAD0A53B20A310> ; data: {
    ....
})
2012-03-28 00:59:55.619 MyApp[517:207] Section 0 with 1 row(s)
2012-03-28 00:59:55.620 MyApp[517:207] Section 1 with 1 row(s)
2012-03-28 00:59:55.620 MyApp[517:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableView.m:995
2012-03-28 00:59:55.621 MyApp[517:207] Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (1) 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 (1 inserted, 0 deleted). with userInfo (null)

Имеет два NSFetchedResultsControllers, управляющих однимUITableView, чьи результаты могут перекрывать не очень хорошая идея?Это приемлемый ответ, но если это возможно, что я могу сделать, чтобы избежать этой ошибки?

1 Ответ

1 голос
/ 28 марта 2012

Я предполагаю, что вы переносите изменения, которые происходят в controller:didChangeObject:... в [tableView beginUpdates] и [tableView endUpdates] вызовах в соответствующих NSFetchedResultsControllerDelegate методах.

Таким образом, каждый NSFRC будет звонить beginUpdates, insertCells и endUpdates.В этом порядке.

Скорее всего, проблема в том, что tableView запрашивает количество разделов и количество строк для каждого раздела после первого вызова endUpdates.На данный момент в одном разделе количество ячеек соответствует результату tableView:numberOfRowsInSection:.К сожалению, он не совпадает в другом разделе, потому что второй fetchedResultsController не выполнил свои методы делегата.Таким образом, второй NSFRC не смог вставить новые ячейки, но он уже возвращает новое количество объектов.

Так как это решить.Я могу думать о трех разных способах.Первый из них самый лучший, последний не следует делать вообще.

Вариант 1. Перепишите модель данных, чтобы один NSFetchedResultsController работал для вас.Просто поместите любой атрибут, который вы используете, чтобы решить, в каком из двух ваших NSFRC объект входит в модель данных, и установите соответствующий sectionKeyPath.Вероятно, вам нужен третий раздел для объектов, которые принадлежат к обоим разделам, потому что один объект не может быть в двух разделах одновременно.

Вариант 2: сбросить NSFetchedResultsController и использовать NSArrays.Если изменения основных данных могут произойти откуда-то еще (например, импорт, который выполняется в фоновом режиме), вам будет трудно, потому что вы должны отслеживать эти изменения самостоятельно.Вы должны отправлять уведомления, когда другой процесс изменяет объекты базовых данных.Вы также должны управлять изменениями, которые происходят с вашим viewController.Вы можете сделать копию старого массива, получить новый массив, сравнить массивы и вставить или удалить ячейки, где это необходимо.

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

  • в controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:

    if (change will cause insert or delete in both sections) {
     if (!delayEndUpdates) {
      self.delayEndUpdates = YES
      self.firstUpdatingController = controller;
      [tableView beginUpdates]; // another beginUpdates should stop the tableView from updating. At least in theory
     }
    }
    else {
     self.delayEndUpdates = NO;
    }
    
  • в controllerDidChangeContent:

    [tableView endUpdates]; // the original
    if (self.firstUpdatingController != controller) {
     // after the second NSFRC did change its content
     if (delayEndUpdates) {
      [tableView endUpdates]; // another one to start tableView updating again
     }
     self.firstUpdatingController = nil;
    }
    

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

...