【问题标题】:TableView with two instances of NSFetchedResultsController带有两个 NSFetchedResultsController 实例的 TableView
【发布时间】:2012-02-18 07:44:42
【问题描述】:

经过几天的研究和重新编码后,我非常难过。我的目标是让一个测试应用程序运行一个由两个单独的 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

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

问题(终于)

当我为未收集的项目选择复选标记时,出现了(第一个)问题。我希望该项目会从它下面列出的部门中删除,并移至“已收集”部分。相反,我得到:

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

如果我尝试相反的操作,则会收到不同的错误:

* 由于未捕获的异常“NSRangeException”而终止应用程序,原因:“* -[__NSArrayM objectAtIndex:]:索引 2 超出范围 [0 .. 1]'

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

任何方向或想法将不胜感激。如果有帮助,我可以提供更多代码,并且还创建了一个带有单个视图的小型测试应用程序来演示该问题。如有必要,我可以上传它,但主要是我想在小型沙箱中测试问题。

更新 - 请求附加代码

根据要求,这是触摸复选标记时发生的情况:

- (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");
    }
}

【问题讨论】:

  • 注意:截图中的部门名称略有偏差。我忘了使用 dept 作为第一个排序描述符。
  • 你如何重新加载你的表?
  • 发布点击复选标记时调用的代码。
  • erurainon - 我相信这是由 aFetchedResultsController.delegate = self(第一个代码块)处理的?
  • jcm - 使用您请求的代码更新。

标签: iphone ios uitableview core-data nsfetchedresultscontroller


【解决方案1】:

好吧,我想我可能已经破解了它。通常情况下,将其剥离并仅阅读一些 cmets 即可为我指明正确的方向。

当我到达委托方法 controllerDidChangeObject 时,我尝试在提供的 indexPath 处插入一行(对于“已检查”项)。除了插入我的附加部分时,这个 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;

我需要为每个插入、删除、更新等执行此操作。我将在验证此内容后将其标记为答案,并为其他 cmets 留出时间。

【讨论】:

  • 也可以检查 if([controller isEqualTo: self.firstFetchedResultsController])
【解决方案2】:

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

【讨论】:

  • 你的意思是controllerWillChangeContent等吗?这些是我从 Apple 的演示中大量提取的。
猜你喜欢
  • 1970-01-01
  • 2012-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-17
  • 1970-01-01
相关资源
最近更新 更多