带两个NSFetchedResultsController实例的TableView

经过几天的研究和重新编码,我几乎陷入了困境。 我的目标是从两个单独的fetchedResultControllers中获取一个运行testing应用程序的单个tableview。

我在购物清单上有一系列物品,每个物品都有一个部门和一个布尔“收集”标志。 未收集的项目应按部门列出,然后是包含所有收集项目(不pipe部门)的单一部分。 当用户检查未收集的项目时,他们应该下移到“收集”部分。 如果他/她没有检查收集到的物品,应该回到正确的部门。

在这里输入图像说明

为了实现第一部分(未收集的项目),我build立了一个简单的fetchedResultsController,用于获取所有收集= NO的项目,并按部门对结果进行分段:

- (NSFetchedResultsController *)firstFRC { // Set up the fetched results controller if needed. if (firstFRC == nil) { // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // fetch items NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; // only if uncollected NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(collected = NO)"]; [fetchRequest setPredicate:predicate]; // sort by name NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // fetch results, sectioned by department NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"department" cacheName:nil]; aFetchedResultsController.delegate = self; self.firstFRC = aFetchedResultsController; } return firstFRC; } 

我设置了行数,节数和节标题,如下所示:

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return firstFRC.sections.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return [[[firstFRC sections] objectAtIndex:section] name]; } 

我还使用样板controllerWillChangeContent,didChangeObject和controllerDidChangeContent来添加/删除单元格作为FRC的更改。

为简洁起见,我将不包括用于显示单元格的代码,但是我基本上是根据单元格的索引path拖动正确的项目,设置单元格的文本/字幕并附加两个复选标记图像之一,具体取决于项目是否收集。 我也连接这个图像(这是在一个button)切换从触摸检查到未选中,并相应地更新项目。

这部分工作正常。 我可以按部门查看我的项目清单,当我将其标记为已收集的项目时,我会看到它按照预期排列。

现在我试图添加底部部分,包含所有收集的项目(在一个部分)。 首先,我设置了第二个fetchedResultsConroller,这次只提取未收集的项目,而不进行分割。 我还必须更新以下内容:

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections - add one for 'collected' section return firstFRC.sections.count + 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section if (section < firstFRC.sections.count) { return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; } else { return secondFRC.fetchedObjects.count; } } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if (section < firstFRC.sections.count) { return [[[firstFRC sections] objectAtIndex:section] name]; } else{ return @"Collected"; } } 

然后我以类似的方式更新了cellForRowAtIndexPath,以便我检索的项目来自正确的FRC:

  Item *item; if (indexPath.section < firstFRC.sections.count) { item = [firstFRC objectAtIndexPath:indexPath]; } else { item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; } [[cell textLabel] setText:[item name]]; …rest of cell configuration 

这在我启动时效果很好。 tableview完全按照预期显示:

在这里输入图像说明

问题(最后)

(第一个)问题出现在我选中未收集项目的复选标记时。 我期望将该项目从其所列的部门中移除,并移至“收集”部分。 相反,我得到:

CoreData:错误:严重的应用程序错误。 在调用-controllerDidChangeContent:期间,NSFetchedResultsController的委托捕获到exception。 无效的更新:部分0中的行数无效。更新(2)后现有部分中包含的行数必须等于更新前(2)部分中包含的行数,加上或减去数字插入或从该部分删除的行(插入1,删除0),加上或减去移入或移出该部分的行数(移入0,移出0)。 与userInfo(null)

如果我试图相反,那么我收到一个不同的错误:

*由于未捕获exception'NSRangeException',原因:'* – [__ NSArrayM objectAtIndex:]:index 2 beyond bounds [0..1]'

我怀疑在这两种情况下,当一个项目从一个FRC移动到另一个FRC时,与FRC中的部分/行数和表格视图的一致性存在问题。 尽pipe第二个错误让我觉得可能有一个更简单的问题与我的检索项目有关。

任何方向或想法将不胜感激。 如果可以的话,我可以提供更多的代码,并且还可以创build一个小testing应用程序,用单个视图来演示这个问题。 我可以上传它,如果有必要,但主要是我想在一个小规模的沙箱testing这个问题。

更新 – 请求附加代码

根据要求,当触摸复选标记时会发生以下情况:

 - (void)checkButtonTapped:(id)sender event:(id)event { NSSet *touches = [event allTouches]; UITouch *touch = [touches anyObject]; CGPoint currentTouchPosition = [touch locationInView:self.tableView]; NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition]; if (indexPath != nil) { [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath]; } } - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { Item *item; if (indexPath.section < firstFRC.sections.count) { item = [firstFRC objectAtIndexPath:indexPath]; } else { item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; } UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; UIButton *button = (UIButton *)cell.accessoryView; if (![item collected]) { [item setCollected:YES]; [button setBackgroundImage:[UIImage imageNamed:@"checked.png"] forState:UIControlStateNormal]; } else if ([item collected]){ [item setCollected:NO]; [button setBackgroundImage:[UIImage imageNamed:@"unchecked.png"] forState:UIControlStateNormal]; } NSError *error = nil; if (![item.managedObjectContext save:&error]) { NSLog(@"Error saving collected items"); } } 

那么,我想我可能已经破解了它。 通常情况下,剥离下来,阅读一些评论指出我在正确的方向。

当我到达委托方法controllerDidChangeObject时,我试图在提供的indexPath(对于“checked”项目)插入一行。 除了当插入我的附加部分,这indexPath没有意识到事实上,有一堆其他部分之前。 所以它接收部分0并尝试插入那里。 相反,如果indexPath来自第二个FRC,我应该通过第一个FRC表中的部分数来递增该部分的编号。 所以,我换了:

 - (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; 

 - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: if ([controller.sectionNameKeyPath isEqualToString:@"department"]) { [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; } else { [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:newIndexPath.row inSection:firstFRC.sections.count]] withRowAnimation:UITableViewRowAnimationFade]; } break; 

我将需要为插入,删除,更新等每一个这样做。我将这个标记为答复后,我已经validation了这一点,并允许其他意见的时间。

你看到的错误意味着你的UITableView在你的NSFetchedResultsController之前重新加载自己。 您发布的代码可能是正确的。 我怀疑问题是在NSFetchedResultsControllerDelegate方法之一。