UITableView由两个具有共同结果的NSFetchedResultsControllers驱动

时间:2012-03-28 08:24:39

标签: core-data uitableview nsfetchedresultscontroller nsfetchrequest

我已经实现了一个带有UITableView的UITableViewController,它由两个NSFetchedResultsControllers(一个包含两个部分的表,每个NSFetchedResultsControllers一个部分)的结果驱动,如前所述discussed here

如果两个NSFetchedResultsControllers都没有共享任何结果,我的代码运行正常并且没有错误。

但是,当我插入一个在我的两个NSFetchedResultsControllers中显示结果的Core Data对象时,我遇到了这个问题:

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 个答案:

答案 0 :(得分:1)

我假设您使用适当的controller:didChangeObject:...方法将[tableView beginUpdates][tableView endUpdates]次调用NSFetchedResultsControllerDelegate中发生的更改包装起来。

因此,每个NSFRC都会调用beginUpdatesinsertCellsendUpdates。按此顺序。

问题很可能是tableView在第一次调用endUpdates后请求每个部分的部分数和行数。此时,在一个部分中,单元格数量与tableView:numberOfRowsInSection:的结果相匹配。不幸的是,它在其他部分不匹配,因为第二个fetchedResultsController没有执行其委托方法。所以第二个NSFRC还没有插入新的单元格,但它已经返回了新的对象数量。

那么如何解决这个问题。我可以想到三种不同的方式。第一个是最好的,最后一个根本不应该完成。

选项1:重写您的数据模型,以便一个NSFetchedResultsController适合您。只需将您使用的任何属性确定为对象所属的2个NSFR中的哪个属于数据模型,并设置适当的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,但我从未尝试过。