经过数天的研究和重新编码,我几乎感到难过。我的目标是使用从两个单独的fetchedResultControllers填充的单个tableview运行测试应用程序。
我在购物清单上有一系列商品,每个商品都有一个部门和一个布尔“收集”标志。未收集的项目应按部门列出,然后是包含所有收集项目的单个部分(无论部门如何)。当用户检查未收集的项目时,它们应该向下移动到“收集”部分。如果他/她取消检查收集的项目,它应该回到正确的部门。
为了实现第一部分(未收集的项目),我设置了一个简单的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的更改。
为简洁起见,我不会包含显示单元格的代码,但我基本上根据单元格的索引路径提取正确的项目,设置单元格的文本/副标题,并附加两个复选标记图像中的一个,具体取决于是否收集该项目。我还会将此图像(位于按钮上)连接起来,以便在触摸时从已选中切换到未选中,并相应地更新项目。
这部分一切正常。我可以按部门查看我的项目列表,当我将其标记为已收集时,我会看到它按预期从列表中删除。
现在我尝试添加底部,包含所有收集的项目(在一个部分中)。首先,我设置了第二个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:错误:严重的应用程序错误。抓住了一个例外 在调用期间来自NSFetchedResultsController的委托 -controllerDidChangeContent :.无效更新:第0部分中的行数无效。现有部分中包含的行数 更新后(2)必须等于包含的行数 更新前的那一节(2),加上或减去行数 从该部分插入或删除(1个插入,0个删除)和加号 或减去移入或移出该部分的行数(0移动 in,0搬出去了)。 with userInfo(null)
如果我尝试相反,那么我会收到一个不同的错误:
*由于未捕获的异常'NSRangeException'终止应用程序,原因:'* - [__ NSArrayM objectAtIndex:]:索引2超出边界[0 .. 1]“
我怀疑在两种情况下,当一个项目从一个FRC移动到另一个FRC时,FRC和tableview中的部分/行数量的一致性存在问题。虽然第二个错误让我觉得可能有一个与我检索项目相关的更简单的问题。
任何方向或想法将不胜感激。如果它有用,我可以提供更多我的代码,并且还创建了一个带有单一视图的小型测试应用程序来演示该问题。我可以在必要时上传它,但大多数情况下我想在小型沙箱中测试这个问题。
更新 - 请求其他代码
根据要求,触摸选中标记时会发生这种情况:
- (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");
}
}
答案 0 :(得分:6)
好吧,我想我可能已经破解了它。通常情况下,剥离它并阅读一些评论指出了我正确的方向。
当我到达委托方法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;
我需要为每个插入,删除,更新等执行此操作。在我验证了这一点后,我会将此标记为答案,并留出时间进行其他注释。
答案 1 :(得分:0)
您看到的错误意味着UITableView
在NSFetchedResultsController
之前重新加载自己。您发布的代码可能是正确的。我怀疑问题出在NSFetchedResultsControllerDelegate
方法之一。