【问题标题】:Getting NSFetchedResultsController, NSSortDescription and sectionNameForKeyPath to work together让 NSFetchedResultsController、NSSortDescription 和 sectionNameForKeyPath 一起工作
【发布时间】:2011-03-20 08:16:49
【问题描述】:

我目前正在开发一个具有几个实体和关系的应用程序,如下所示:

项目 类别

我目前正在获取 Item 实例并使用该项目的 category.name 在部分中显示它们。在这种情况下,我可以使用排序描述符按名称对类别进行排序,这非常简单并且工作正常(下面的相关代码):

-(NSFetchedResultsController*)fetchedResultsController {
    if (fetchedResultsController_ != nil)
        return fetchedResultsController_;
    NSManagedObjectContext *moc = [order_ managedObjectContext];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:moc];
    [fetchRequest setEntity:entity];
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"category.name" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];
    [sortDescriptors release];
    [sortDescriptor release];
    NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
                                          initWithFetchRequest:fetchRequest
                                          managedObjectContext:moc
                                          sectionNameKeyPath:@"category.name"
                                          cacheName:nil];
    controller.delegate = self;
    self.fetchedResultsController = controller;
    [controller release];
    [fetchRequest release];

    NSError *error = nil;
    if (![fetchedResultsController_ performFetch:&error]) {
        // Error handling
    }
    return fetchedResultsController_;
}

我现在的问题是我需要对这些类别进行排序,而不是按名称,而是按 Category 实体的(NSNumber*) displayOrder 属性。 但是我需要 tableview 上的部分标题才能继续使用该类别的 name

如果我将 sortDescriptor 设置为使用 category.displayOrder 并将 sectionNameKeyPath 保持为 category.name,则部分标题可以正常工作,但 sortDescriptor 会被 fetchedResultsController 忽略,并且表格部分按类别名称排序(不知道为什么??)。

我的下一个想法是覆盖displayOrder getter 方法,但这并没有让我走得太远,因为返回类型不同,而且我需要实际的 displayOrder 值来进行部分排序。

所以现在我有一个感觉有点笨拙的解决方案(下面的代码),我想知道是否有更好的方法来单独使用 fetchedResultsController 来实现同样的事情。

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
// The code below grabs a reference to first object for a given section
// and uses it to return the associated category name
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    NSArray *menuItems = [sectionInfo objects];
    if ([menuItems count] > 0)
    {
        MenuItem *menuItem = [menuItems objectAtIndex:0];
        NSString *categoryName = menuItem.category.name;
        return categoryName;
    }
    return [sectionInfo name];
}

我在这里缺少一些基本的东西吗?

提前感谢您的想法。

罗格

【问题讨论】:

  • 您看过 Apple 的 The Elements 示例项目吗?我记得,它处理所有这些东西。

标签: objective-c ios core-data nsfetchedresultscontroller


【解决方案1】:

这是解决问题的完美方法,罗格。

您当然不希望/不需要继承 NSFetchedResultsController。

@aroth,我们没有足够的信息来了解他的对象模型的详细信息,但名称肯定暗示该类别不拥有项目。项目有一个类别。他的意图是显示一个项目列表,这就是他获取项目的原因。

【讨论】:

  • 对于延迟返回此问题表示歉意。最后,我采用了最初的解决方案,它似乎对我想要实现的目标运行良好。
  • 'Own' 可能不是最好的术语,但如果每个Item 都有一个Category,那么每个Category 都有一个Items 的集合。他的意图是显示一个项目列表,按部分分组并按类别排序。因此,当您可以获取分类的排序列表并简单地使用[category items] 获取属于每个类别的所有项目时,获取项目然后尝试手动分组和排序它们是没有意义的。以迭代方式完成,将以正确的顺序构建所需的项目列表,无需自定义排序/分组。
【解决方案2】:

就排序而言,the documentation 有这样的说法:

如果控制器生成节,则数组中的第一个排序描述符用于将对象分组为节;其键必须与 sectionNameKeyPath 相同,或者使用其键的相对排序必须与使用 sectionNameKeyPath 的相对排序匹配。

在英语中(你可能已经知道这个 Rog,但你可能不知道,当然后来搜索这个的人可能会喜欢这个解释),这意味着如果你使用部分,那么 NSFetchRequest 上的排序必须对所有项目进行分组在同一个部分一起。这可以通过将第一个排序条件设置为用作部分名称的字段,或者可以通过将第一个排序条件设置为导致相同类型分组的其他内容。

文档没有说明如果你搞砸了会发生什么;它可能会完全搞砸部分名称,重复部分,跳过部分,检测情况并“修复”您的排序,甚至只是崩溃。您的任何类别都有相同的 displayOrder 吗?

你的解决方案当然是可行的,如果你不能让它正常工作,按 displayOrder 排序,同时按 category.name 为部分命名,它可能是你最好的解决方案。

【讨论】:

    【解决方案3】:

    为什么要获取 Item 而不是 Category?如果我正确理解您的关系,CategoryItem 拥有一对多的关系,因此理论上Category 实例应该有一个“项目”属性,该属性返回该类别中的每个Item

    如果是这种情况,那么您可以简单地获取所有类别,然后按displayOrder 对它们进行排序。然后,忘记在NSFetchedResultsController 本身中使用部分。相反,您关联的 tableView 方法看起来像:

    - (NSInteger)numberOfSections {
        return [[self.fetchedResultsController fetchedObjects] count];
    }
    
    - (NSInteger)numberOfRowsInSection:(NSInteger)section {
        Category* category = [[self.fetchedResultsController fetchedObjects] objectAtIndex:section];
        return [[category items] count];
    }
    
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
        Category* category = [[self.fetchedResultsController fetchedObjects] objectAtIndex:section];
        return category.name;
    } 
    

    简而言之,我认为您通过获取Item 而不是Category 并试图让NSFetchedResultsController 为您管理您的部分分组,使事情变得过于复杂。它更简单,需要更少的代码来自己进行部分管理。

    【讨论】:

    • 我遇到了类似的问题,正在阅读您的解决方案。如果 Item 也有 displayOrder,你会怎么做? Category.items 是一个 NSSet,你会如何通过 displayOrder 对 Items 进行排序?
    • @Zyphrax - 如果需要,我会在NSSet 上调用sortedArrayUsingDescriptors 以获得一致排序的列表。如reference docs 中所述。
    • 谢谢。什么时候打电话给sortedArrayUsingDescriptors 合适?这将如何与UITableView 一起使用? FetchedRC 发现的新项目不会扰乱秩序吗?
    • @Zyphrax - 你可以在cellForRowAtIndexPath: 中调用它,如果你实现了那个方法,也可以在didSelectRowAtIndexPath: 中调用它。基本上在您想知道哪个Item 对应于表中的特定位置的任何地方。新项目是一个需要处理的问题,无论数据如何实际进入表。它们确实会影响顺序,是的,因此每当数据集更改时更新/重新加载受影响的部分非常重要。
    【解决方案4】:

    您可能需要继承 NSFetchedResultsController 并自定义部分名称功能。请参阅有关子类化的 NSFetchedResultsController 类文档。

    【讨论】:

    • 如果要自定义创建部分和索引标题,请创建此类的子类。您覆盖 sectionIndexTitleForSectionName: 如果您希望部分索引标题不是部分名称的大写首字母。如果您希望索引标题不是通过在所有已知部分上调用 sectionIndexTitleForSectionName: 创建的数组,则覆盖 sectionIndexTitles。
    猜你喜欢
    • 1970-01-01
    • 2013-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-02
    • 2013-04-05
    相关资源
    最近更新 更多