Reputation: 13511
I'm building an expense tracker where an Expense
can belong to only one Category
but can have multiple Tag
s. This is my object graph:
In the screen where I list all the expenses in a table view, I want the expenses to be grouped by date (the sectionDate
), and then by Category
(or, using a segmented control, by Tag
). This is the intended UI:
I can already make an NSFetchedResultsController
query all expenses, section them by date, then by category, but I can't get the (1) total for the category and (2) the list of expenses in it. How might I proceed to do that? This is my current code:
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
}()
Upvotes: 3
Views: 373
Reputation: 13511
I appreciate @pbasdf's answer, but I feel that I'll have a hard time wrapping my head around the solution after a long time of not looking at the code.
What I've come around to doing is instead of fetching Expense
objects, I defined a new entity for the subsections themselves (CategoryGroup
, and I will also make a TagGroup
) and fetch those entities instead. These entities have references to the Expense
objects that they contain, and the Category
or the Tag
that represents the group. This is my (partially complete) data model:
And my NSFetchedResultsController
is now far simpler in code:
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")
}()
The downside is that I now have to write extra code to make absolutely sure that the relationships among the entities are correctly defined whenever an Expense
or a Category
is created/updated/deleted, but that's an acceptable tradeoff for me as it is easier to comprehend in code.
Upvotes: 0
Reputation: 21536
A should forewarn that I've never done this, but personally I would set about it as follows:
propertiesToGroupBy
: it forces you to use the .dictionaryResultType
which means you can only access the underlying managed objects by executing a separate fetch.sectionDate
and the category.name
. This property will be used as the sectionNameKeyPath
for the FRC, so that the FRC will establish a section in the tableView for each unique combination of sectionDate and category name.category.name
as another sort descriptor for the fetch underlying the FRC. This will ensure that the Expense
objects fetched by the FRC are in the correct order (ie. all Expense
objects with the same sectionDate and category name are together).name
property for the section (from the FRC) will include both the sectionDate and the category name. In most cases, you can strip out and ignore the sectionDate, displaying only the category name and corresponding total (see below). But for the very first section, and indeed the first section for any given sectionDate, add an additional view (to the section header view) showing the sectionDate and overall total for that sectionDate.numberOfRowsInSection
datasource method; if expanded use the figure provided by the FRC.objects
array for the relevant section (or use a suitable .reduce
to achieve the same).fetchedObjects
for the FRC to include only the Expense
objects for the relevant sectionDate
, and then iterate or .reduce the filtered array.I am happy to add or amend if any of that needs clarification.
Upvotes: 1