UITableView 由两个 NSFetchedResultsController 驱动,结果通用
Posted
技术标签:
【中文标题】UITableView 由两个 NSFetchedResultsController 驱动,结果通用【英文标题】:UITableView driven by two NSFetchedResultsControllers with common results 【发布时间】:2012-03-28 08:24:38 【问题描述】:我已经实现了一个带有 UITableView 的 UITableViewController,它由两个 NSFetchedResultsControllers(一个包含两个部分的表,每个 NSFetchedResultsControllers 一个部分)的结果驱动,如之前的discussed here。
如果两个 NSFetchedResultsController 不共享任何结果,我的代码可以正确运行且没有错误。
但是,当我插入一个作为结果显示在我的两个 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)
是否有两个 NSFetchedResultsController 驱动一个 UITableView,其结果可能重叠不是一个好主意?这是一个可以接受的答案,但如果这应该是可能的,我该怎么做才能避免这个错误?
【问题讨论】:
【参考方案1】:我假设您将controller:didChangeObject:...
中发生的更改包装在[tableView beginUpdates]
和[tableView endUpdates]
调用中的适当NSFetchedResultsControllerDelegate
方法中。
所以每个 NSFRC 都会调用 beginUpdates
、insertCells
和 endUpdates
。按照这个顺序。
最有可能的问题是tableView 在第一次调用endUpdates
之后请求了节数和每个节的行数。此时在一个部分中,单元格的数量与来自tableView:numberOfRowsInSection:
的结果相匹配。不幸的是,它在其他部分不匹配,因为第二个 fetchedResultsController 没有执行它的委托方法。所以第二个 NSFRC 还不能插入新的单元格,但它已经返回了新的对象数量。
那么如何解决这个问题。我能想到三种不同的方法。第一个是最好的,最后一个根本不应该做。
选项 1:重写您的数据模型,以便一个 NSFetchedResultsController 为您工作。只需将用于确定对象属于数据模型的 2 个 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,但我从未尝试过。
【讨论】:
你是对的,我的NSFetchedResultsControllerDelegate
方法确实调用了[tableView beginUpdates]
和[tableView endUpdates]
,这也是正确的:tableView 在第一个之后请求节数和每个节的行数致电endUpdates
。我将遵循您的第一种方法的修改版本。我想我仍然需要两个NSFetchedResultController
s,因为我在我的部分中需要两个不同的排序顺序,但我可以避免在两个不同的部分中有相同的对象。以上是关于UITableView 由两个 NSFetchedResultsController 驱动,结果通用的主要内容,如果未能解决你的问题,请参考以下文章