NSFetchedResultController sort table view sections by multiple keys - ios

Problem:
in my CoreData model, i have two entities called List and Item, they have one-to-many relationship. There are 2 types of List in my current design, system list and user list. System list is created by system during the very first launch and user list will be created by users anytime.
In my TVC, i would like to present all the items grouped by their associated lists(use list name as section title), in addition, i want all the system lists always placed on top of the user lists.
For instance, consider i have all the instances of list in DB as below:
System list: Inbox
User list: FruitList, ShoppingList
The expected result will be:
Inbox
InboxItem1
InboxItem2
FruitList
FruitListItem1
ShoppingList
Apple
Orange
The Approach:
With FRC, i can a achieve the first goal by setting sectionNameKeyPath as "list_name" to the FRC. But i have problem with fixing position the system list (Inbox in this case, it should always at index 0). However, the problem comes as user input their list with name start with "A" - "J" and all those lists will be placed on top of inbox, and i did try to fix it by adding "list_type" as secondary sort key, but it doesn't work.
NSFetchRequest *requst = [[NSFetchRequest alloc]initWithEntityName:ENTITY_ITEM];
NSArray *sortDescriptors = #[
[NSSortDescriptor sortDescriptorWithKey:#"belongsToList.list_name" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"belongsToList.list_type" ascending:NO]];
requst.sortDescriptors = sortDescriptors;
requst.predicate = [self predicateForListType:type selectedList:list];
requst.fetchBatchSize = 20;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:requst managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"belongsToList.list_name" cacheName:nil];
Temporary solution:
The temp solution i can think of to solve this problem is to add prefix string to the list name and store them as another attribute from DB and use it as sectionNameKeyPath.
e.g.
"0-" - for system lists ("Inbox" -> "0-Inbox")
"1-" - for user list ("ShoppingList" -> "1-ShoppingList")
and remove the prefix in -tableView:titleForHeaderInSection: . But this solution creates overhead and i think there should be much better solution out there since it is a common problem that all developers will face while they are dealing with FRC and TBV.
Any input??!!!

You can add a transient attribute like sectionIdentifier to the List entity and sort like this: use
[NSSortDescriptor sortDescriptorWithKey:#"list.sectionIdentifier" ascending:YES]
as the first sort descriptor.
The sectionIdentifier could be anything which sorts the way you intend ("System" first) and from which you can reconstruct the proper section name to return in titleForHeaderInSection.

Related

NSFetchRequest with no duplications by parameter

I have a simply BWWishlistBook entity which has a property BWBook. One BWBook may belong to many BWWishlistBooks and one BWWishlistBook always have only one BWBook.
Now I have a NSFetchRequest for BWWishlistBook:
let fetchReguest = NSFetchRequest(entityName: "BWWishlistBook")
I need to avoid to fetch wishlist books with the same book inside. is it possible? How to setup predicate then? BWBook has an unique id property.
In MySQL this is DISTINCT. What is in CoreData?
-returnsDistinctResults
/* Returns/sets if the fetch request returns only distinct values for the fields specified by propertiesToFetch.
This value is only used for NSDictionaryResultType.
Defaults to NO. */
#available(iOS 3.0, *)
public var returnsDistinctResults: Bool
Make a fetch on BWBook with a predicate of wishlist.count != 0.
Now you will have a list of books (all distinct) and all of them will have at least one wishlist associated with them.
Now you can just display the first wishlist from each book and you will have a list of wish lists with distinct books.
If you know which BWBook for which you want to fetch BWWishlistBook the easiest (and most performant way) is the fetch it by the relationship on the BWBook.
Maybe something along the lines of
wishlist = bwBook.bwWishlistBooks

Fetching latest value from core data and assigning it to a label. Swift

I've only been coding for 2 weeks now and I am trying to build a 'weights logger app'. I am saving the weights under the entity - "Weights" with attributes for "benchPress", "deadLift" etc.
I figured out how to save values. So when the user clicks save, it saves the recorded value to the array.
But I don't know how to fetch the 'latest' value and set it equal to a UILabel.
Image showing the entity
The code I have so far to fetch the result
Any help would really be appreciated. I've been stuck on this for a while now. Thanks!
Core Data doesn't have any concept of "latest". It doesn't track when new data has been saved. If you need something like the most recent entry, you need to add your own field-- a date, or an integer index, or something that would indicate the correct instance to fetch.
If you added a date field named date, you'd get the entry with the most recent date using a fetch request something like this:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Weights"];
NSSortDescriptor *dateSort = [NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO];
fetch.sortDescriptors = #[ dateSort ];
fetch.fetchLimit = 1;
The result would be an array containing at most one object, which would have the most recently saved date.

CoreData fetch with NSSortDescriptor by count of to-many relationship

I have 2 CoreData entities (Person, Pet) with the following relationship:
A Person can have multiple Pets. So, there is a To-Many relationship from Person->Pet.
I want to fetch all Persons, sorted by the count of Pets that they have where the person with the most Pets is first, and the person with the fewest Pets is last.
When fetching a list of Persons, I tried using an NSSortDescriptor like the following:
[NSSortDescriptor sortDescriptorWithKey:#"pets.count" ascending:NO]
I thought that the "pets" property on a "person" would allow me to use the count property of NSSet to sort. Alas, CoreData complains with this error:
Fetch exception to-many key not allowed here
Is this a completely wrong approach? How am I supposed to do something like this with a CoreData fetch?
Thanks in advance for your help.
Try using collection operators:
[NSSortDescriptor sortDescriptorWithKey:#"pets.#count" ascending:NO]
This can't be applied directly though (to a fetch request), you need to execute the fetch and then run the sort on the result array (sortedArrayUsingDescriptors:).
If you want to sort while fetching, you would need to add a (non-transient) attribute to the entity which holds the count number.
Change the format of your key to #"pets.#count"
Take a look at this answer https://stackoverflow.com/a/20317087/2567126
Basically you create a persistent property on the NSManagedObject and update the property count any time a child object is added or removed from the relationship.
I also need to do the same thing but in Swift. I just to follow:
Fetch the request from CoreData without 'count' key-value.
I performed sort (high order function) on the fetched result.
allCategory.sort { (category1, category2) -> Bool in
return category1.tasks?.count > category2.tasks?.count
}
This is working for me.

iOS NSFetchedResultsController: sort sections by >=2 properties

I use NSFetchedResultsController to fetch from database with Core Data. And I have an Entity with 2 properties, prop1 and prop2 of NSString.
How would I sort sections not only by one of the properties but both?
Now it is:
Title1ForProp1/Title2ForProp2 (prop1==1 prop2==2)
Title1ForProp1/Title1ForProp2 (prop1==1 prop2==1)
Title2ForProp1/Title1ForProp2 (prop1==2 prop2==1)
I need:
Title1ForProp1/Title1ForProp2 (prop1==1 prop2==1)
Title1ForProp1/Title2ForProp2 (prop1==1 prop2==2)
Title2ForProp1/Title1ForProp2 (prop1==2 prop2==1)
When you create the fetch request for the NSFC you create the sort descriptor and give the request an array.
You can put as many sort descriptors in the array as you like.
Just create a sort descriptor for each field you want to sort by.
I can remember which order you have to put them into the array though.
OK, so code wise...
NSSortDescriptor *sd1 = [[NSSortDescriptor alloc] initWithKey:#"prop1" ascending:YES];
NSSortDescriptor *sd2 = [[NSSortDescriptor alloc] initWithKey:#"prop2" ascending:YES];
[fetchRequest setSortDescriptors:#[sd1, sd2]];
This is all you have to do.
The NSFC will only split them into sections if you give it a sectionNameKeyPath. If you don't want any sections then make the sectionNameKeyPath nil.
Fogmeister's sort descriptors array is appropriate (sort on prop1 then prop2) but if you provide prop1 in your sectionNameKeyPath your sections would be broken up only by prop1. Within each section, the items would be sorted by both prop1 and prop2.
If this is not what you want and you need to additionally group your results into sections by both prop1 and prop2, you probably want to create a transient property that concatenates both prop1 and prop2 and provide that transient property as your sectionNameKeyPath. This provides not just the title for the section but also determines how results are grouped into sections.
Take a look at this question for how you might create a transient property for your section names:
NSFetchedResultsController with sections created by first letter of a string

Core Data with to Many relationship - Creating a NSPredicate with ALL in SUBQUERY

My object graph looks like this
SnapShot -->> Pane --> ManagedImage
I'm trying to find a SnapShot that has the exact ManagedImages contained with in a set.
The code I've got now returns an Array of SnapShots that have one or more of the ManagedImages that are in the set. I then search through the Array to find the correct SnapShot but I'm guessing it would be much faster to filter in the Subquery
With an NSPredicate how can I get the unique SnapShot that has ALL of the ManagedImages that are in the set?
Here's my code
mySet = ... // A unique set of (usually 3) managedImages that I'm trying to find a snapShot for
NSFetchRequest *request = ...
request.entity = [NSEntityDescription entityForName:#"SnapShot" inManagedObjectContext:[self managedObjectContext]];
// Want this to work but sends an exception
//request.predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(self.panes, $pane, ALL $pane.managedImage IN %#).#count != 0", mySet];
// Using this
request.predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(self.panes, $pane, $pane.managedImage IN %#).#count != 0", mySet];
A good rule of thumb is that if you already have managed objects in hand, you don't fetch but instead walk the relationships from the managed objects you have to the managed objects you want.
So, your relationship graph probably actually looks like this:
SnapShot <-->> Pane <--> ManagedImage
or maybe:
SnapShot <<-->> Pane <<--> ManagedImage
Since you have a set of ManagedImage objects all you have to do is walk the keypath of pane.snapShot or panes.snapShots to find the SnapShot objects associated with each ManagedImage object. Then you just extract the unique SnapShot objects.
In the first case, the matter is trivial because of the one-to-one relationship path of
ManagedImage-->Pane-->SnapShot
In the second case, you will need to first get all the unique SnapShot objects:
NSSet *shots=[aMangedImageObj valueForKeyPath:#"distinctUnionOfSets.panes.snapShots"];
... for each ManagedImage instances and then merge all the sets with setByAddingObjectsFromSet: or a similar method to produce a single set of unique objects.
Fetches should be used to find the first objects in graph that you need but once you have the objects, you don't fetch but walk the relationships. Otherwise, there is not much point to having relationships in the first place.

Resources