如何在NSFetchedResultsController中构建子部分
问题描述:
我正在构建费用跟踪器,其中Expense
只能属于一个Category
,但可以有多个Tag
s。这是我的对象图:如何在NSFetchedResultsController中构建子部分
在我列出所有的费用表视图的画面,我想费用由日期(sectionDate
)进行分组,然后由Category
(或者,使用分段控制,通过Tag
)。这是预期的UI:
我已经可以做出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
}()
答
我很欣赏@ pbasdf的回答,但是我觉得在经过很长一段时间没有看代码之后,我会很难把头埋在解决方案中。
我是来干什么的周围,而不是获取Expense
对象,我定义的小节自己一个新的实体(CategoryGroup
,我也会让TagGroup
),并获取这些实体代替。这些实体引用了它们包含的Expense
对象,以及代表该组的Category
或Tag
。这是我的(部分完成)的数据模型:
而且我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")
}()
的缺点是,我现在必须编写额外的代码,使绝对确保在创建/更新/删除Expense
或Category
时正确定义实体之间的关系,但对我来说这是一个可接受的折衷方案,因为它更易于在代码中理解。
答
A应该预先警告,我从来没有这样做,但我个人会设置一下就可以如下:
- 不要使用
propertiesToGroupBy
:它迫使你使用.dictionaryResultType
这意味着您只能通过执行单独的提取访问底层的托管对象。 - 相反,将其他计算属性添加到相关的NSManagedObject子类中,合并
sectionDate
和category.name
。此属性将用作FRC的sectionNameKeyPath
,以便FRC将为tableDate和类别名称的每个唯一组合在tableView中建立一个部分。 - 将
category.name
添加为FRC底层的另一个排序描述符。这将确保FRC提取的Expense
对象的顺序正确(即所有具有相同节日日期和类别名称的Expense
对象在一起)。 - 为每个部分添加节标题视图。部分(来自FRC)的
name
属性将包含sectionDate和类别名称。在大多数情况下,您可以删除并忽略sectionDate,只显示类别名称和相应的总数(见下文)。但是对于第一部分以及任何给定部分日期的第一部分,添加一个额外的视图(部分标题视图),显示sectionDate的sectionDate和总计。 - 判断一个给定的部分是否是sectionDate的第一部分有点棘手。您可以检索上一节的名称并比较sectionDates。
- 要折叠/展开节,请维护一个数组来保存每个节的折叠/展开状态。如果该部分已折叠,请在tableView
numberOfRowsInSection
数据源方法中返回0;如果扩大使用FRC提供的数字。 - 对于类别总数,遍历相关部分的
objects
数组(或使用合适的.reduce
实现相同)。 - 对于sectionDate合计,筛选
fetchedObjects
以使FRC仅包含相关sectionDate
的Expense
对象,然后迭代或.reduce已过滤的数组。
如果有任何需要澄清,我很乐意补充或修改。
是'sectionDate'完全相同的所有项目在该日期组? –
@JonRose是的。我有'Expense'的扩展名,每当'dateSpent'被修改时它会设置'sectionDate'。 'sectionDate'是只有MM/DD/YY组件的'dateSpent'。 'sectionDate'本身也存储在核心数据中 - 它不是一个瞬态属性。 –