CoreData sort using two fields - ios

I am trying to sort CoreData entity by two fields schedule_order and activity_order. I want to sort by schedule_order then keep it sorted by that and sort by activity_order while keeping the schedule_order in tact.
I have tried this:
let sortDescriptor = NSSortDescriptor(key: "schedule_order", ascending: true)
let sortDescriptor1 = NSSortDescriptor(key: "activity_order", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor,sortDescriptor1]
Do I need to do this a different way? I have one case where it isn't sorting it correctly. The one that should be appearing first keeps up ending up at the end.

Related

How to group Core Data objects as the data source for a UITableView

In Core Data, I have a large number of "City" objects, each of which has a "country" property.
Rather than have a very long scrollable list of cities, I need to have a separate screen which lists just Countries in a UITableView. The user then selects a country and is taken to a new screen, which has the Cities for that Country.
Note: This is for a game, so the UITableView data source is bound to a SKScene, but I don't think that should make any difference as it works fine for other screens.
I have figured out the detail screen which displays the Cities for a specific country and it works fine in test with hard-coded data.
I am using Swift 5 so my data calls are like this:
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "City")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
fetchRequest.predicate = NSPredicate(format: "country = %#", country)
try fetchedResultsController.performFetch()
However for the other screen, I don't know how to "group" the Countries, which are the property on each City, and make the groupings the data source for the UITableView on the other screen.
a) I could create an array of Countries, but how would I have the fetchedResultsController return the Country data to UITableView?
b) Or, is it possible to group or filter objects within a fetchedResultsController?
c) Or, is it possible to do SQL-like grouping within a fetch request?
Thanks
Thanks #Larme for the link to https://developer.apple.com/documentation/coredata/nsfetchrequest/1506191-propertiestogroupby
So to get this working, Results Container needs to be defined with NSDictionary.
var fetchedResultsController: NSFetchedResultsController<NSDictionary>!
The fetchRequest needs to be set to group and fetch the specific property.
fetchRequest.propertiesToFetch = ["country"]
fetchRequest.propertiesToGroupBy = ["country"]
fetchRequest.resultType = .dictionaryResultType
Then in the methods where we need to access the data:
// cell data
let countryName = (fetchedResultsController.object(at: indexPath) as NSDictionary)["country"]!
cell.textLabel?.text = "\(countryName)"
Thanks for pointing me in the right direction!

how to fetch last n records in CoreData using fetchedResultsController

I have a table Student which has two attributes Name and DateOfBirth.
I want to display names of students in a UITableView in descending order of their DateOfBirth that is younger one on top and older on bottom of the list.
I have created a FetchRequest that fetches all the students and displays in descending order of their DateOfBirth.
let studentFetchRequest = Student.fetchRequest()
let dateSortDescriptor = NSSortDescriptor(key: #keyPath(Student.dateOfBirth), ascending: false)
studentFetchRequest.sortDescriptors = [dateSortDescriptor]
let frc = NSFetchedResultsController<Student>(fetchRequest: studentFetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil)
frc.performFetch()
Now, I have a requirement that I need to display only last 15 students. Considering Student table has 50 records.
I tried changing the sortDescriptor's ascending property to true and added fetchLimit of 15 to my studentFetchRequest to fetch only last 15 students.
It fetches the last 15 students but it reverses the order in which they are being listed in UITableView. i.e. older students on top and younger on the bottom.
How can I modify the fetchRequest and still be able to use a FetchedResultsController to display the last 15 students in my UITableView?
I need to use FetchedResultsController because the actual table is far complex, I have simplified the question to explain the situation.

How to build subsections in an NSFetchedResultsController

I'm building an expense tracker where an Expense can belong to only one Category but can have multiple Tags. 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
}()
A should forewarn that I've never done this, but personally I would set about it as follows:
Don't use propertiesToGroupBy: it forces you to use the .dictionaryResultType which means you can only access the underlying managed objects by executing a separate fetch.
Instead, add another computed property to the relevant NSManagedObject subclass, combining the 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.
Add 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).
Add a section header view for each section. The 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.
Working out whether a given section is the first section for the sectionDate is a little tricky. You could retrieve the name for the previous section and compare the sectionDates.
To collapse/expand the sections, maintain an array holding the collapsed/expanded state of each section. If the section is collapsed, return 0 in the tableView numberOfRowsInSection datasource method; if expanded use the figure provided by the FRC.
For the category totals, iterate through the objects array for the relevant section (or use a suitable .reduce to achieve the same).
For the sectionDate totals, filter the 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.
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.

Sorting data by multiple key values

I have a basic list app with two entities Folder and Item with a to many relationship, the folder subclass has a function called itemsArray() which returns the Items in a sorted array by creationDate which are displayed UITableView.
Item has two properties called date of type NSDate and another of type bool called completed. I have created a function where I can mark the cell item object as completed which changes the cell text color to grey.
Is there a way I can also sort it by its completed value ? For e.g the completed value of true or false should separate all the completed items and then also sort it by the creationDate.
The end result should be where the completed items are at the bottom of the UITableView and in order of date as well as the non completed items are in order of date. As if completion is higher priority than the date but still orders by creationDate
Heres my current function with sort descriptor to return an array;
func itemsArray() -> [Item] {
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: true)
return checklist.sortedArrayUsingDescriptors([sortDescriptor]) as! [Item]
}
PS: I tried some work arounds such as when marking as completed updating the date so it is at the bottom. This separates the completed items with the non completed. But when unmarking the item it remains at the bottom also this isn't a good long term solution as I may want to change the sorting to alphabetical order and completion.
You can add another NSSortDescriptor to the function:
func itemsArray() -> [Item] {
let sortByCompleted = NSSortDescriptor(key: "completed", ascending: true)
let sortByCreationDate = NSSortDescriptor(key: "creationDate", ascending: true)
return checklist.sortedArrayUsingDescriptors([sortByCompleted, sortByCreationDate]) as! [Item]
}
Play with the ascending value to get true first or false first. Or the Swifty way of doing things:
func itemsArray() -> [Item] {
return checklist.sort {
if $0.completed == $1.completed {
return $0.creationDate < $1.creationDate
}
return $0.completed < $1.completed
}
}

Swift how to have a TableView of hashs

New to swift and mobile at all.
Created the default Master project in Xcode and trying to have a table (tableView) of categories where each category has an int next to it
House 1
Child 4
Food 5
That's it.
The default cells are just time stamp like so
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
I presume I will want a hash But i can't even set something other than a timestamp in that field
tried
newManagedObject.setValue(1, forKey: "timeStamp")
instead of
newManagedObject.setValue(NSDate.date(), forKey: "timeStamp")
get an error
Unacceptable type of value for attribute: property = "timeStamp"; desired type = NSDate; given type = __NSCFNumber; value = 1.'
just FYI, my master controller is like so
MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate

Resources