如何在NSFetchedResultsController中构建子部分

时间:2017-06-04 05:02:32

标签: ios swift cocoa-touch core-data

我正在构建费用跟踪器,其中Expense只能属于一个Category,但可以有多个Tag。这是我的对象图:

enter image description here

在我列出表格视图中所有费用的屏幕中,我希望按日期(sectionDate),然后按Category(或使用分段控件)对费用进行分组,Tag)。这是预期的用户界面:

enter image description here

我已经可以NSFetchedResultsController查询所有费用,按日期划分,然后按类别划分,但我无法获得该类别的(1)总数和(2)费用清单在里面。我该怎么办呢?这是我目前的代码:

let fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> = {
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Expense")
    fetchRequest.resultType = .dictionaryResultType

    fetchRequest.sortDescriptors = [
        NSSortDescriptor(key: #keyPath(Expense.sectionDate), ascending: false)
    ]

    fetchRequest.propertiesToFetch = [
        #keyPath(Expense.sectionDate),
        #keyPath(Expense.category)
    ]
    fetchRequest.propertiesToGroupBy = [
        #keyPath(Expense.sectionDate),
        #keyPath(Expense.category)
    ]

    let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                              managedObjectContext: Global.coreDataStack.viewContext,
                                                              sectionNameKeyPath: #keyPath(Expense.sectionDate),
                                                              cacheName: nil)
    return fetchedResultsController
}()

2 个答案:

答案 0 :(得分:1)

A应该预先知道我从未这样做过,但我个人会按照以下方式进行讨论:

  1. 不要使用propertiesToGroupBy:它会强制您使用.dictionaryResultType,这意味着您只能通过执行单独的提取来访问基础托管对象。
  2. 而是将另一个计算属性添加到相关的NSManagedObject子类,将sectionDatecategory.name组合在一起。此属性将用作FRC的sectionNameKeyPath,以便FRC将在tableView中为sectionDate和类别名称的每个唯一组合建立一个部分。
  3. 添加category.name作为FRC底层提取的另一个排序描述符。这将确保FRC获取的Expense个对象的顺序正确(即具有相同sectionDate和类别名称的所有Expense个对象在一起)。
  4. 为每个部分添加部分标题视图。该部分的name属性(来自FRC)将包括sectionDate和类别名称。在大多数情况下,您可以去掉并忽略sectionDate,只显示类别名称和相应的总数(见下文)。但是对于第一部分,实际上是任何给定sectionDate的第一部分,添加一个额外的视图(到部分标题视图),显示该部分日期的sectionDate和总计。
  5. 确定给定的部分是否是sectionDate的第一部分有点棘手。您可以检索上一部分的名称并比较sectionDates。
  6. 要折叠/展开各个部分,请维护一个包含每个部分的折叠/展开状态的数组。如果该部分已折叠,则在tableView numberOfRowsInSection数据源方法中返回0;如果扩展使用FRC提供的数字。
  7. 对于类别总计,请遍历相关部分的objects数组(或使用合适的.reduce来实现相同的目标)。
  8. 对于sectionDate totals,过滤FRC的fetchedObjects以仅包含相关Expense的{​​{1}}个对象,然后迭代或.reduce过滤后的数组。
  9. 如果有任何需要澄清,我很乐意添加或修改。

答案 1 :(得分:0)

我很欣赏@pbasdf的答案,但我觉得在经过很长一段时间不查看代码后,我将很难绕过解决方案。

我接下来要做的不是获取Expense个对象,而是为子节本身定义了一个新实体(CategoryGroup,我还会创建一个TagGroup)并改为获取这些实体。这些实体引用了它们包含的Expense个对象,以及代表该组的CategoryTag。这是我的(部分完整的)数据模型:

enter image description here

我的NSFetchedResultsController现在在代码中更简单了:

let fetchedResultsController: NSFetchedResultsController<CategoryGroup> = {
    let fetchRequest = NSFetchRequest<CategoryGroup>(entityName: "CategoryGroup")
    fetchRequest.sortDescriptors = [
        NSSortDescriptor(key: #keyPath(CategoryGroup.sectionDate), ascending: false)
    ]
    return NSFetchedResultsController(fetchRequest: fetchRequest,
                                      managedObjectContext: Global.coreDataStack.viewContext,
                                      sectionNameKeyPath: #keyPath(CategoryGroup.sectionDate),
                                      cacheName: "CacheName")
}()

缺点是我现在必须编写额外的代码以确保在创建/更新/删除ExpenseCategory时正确定义实体之间的关系,但这是对我来说是可接受的权衡,因为在代码中更容易理解。