NSFetchedResultsController section sorting by class - ios

I need to sort my objects into sections using
[[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:sectionName
cacheName:nil];
I would like to sort them by their class. For example, objects of type MyObjectType go into one section and objects of type OtherObjectType go into a second section, where both object types will appear in the results because OtherObjectType inherits from MyObjectType. Passing #"class" as the sectionNameKeyPath parameter in the above method appears to work. However, to get the proper sorting I also need the NSFetchRequest's sort descriptor to sort based on class:
NSSortDescriptor *sectionDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"class"
ascending:YES];
Using this sort descriptor gives me the error Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath class not found in entity.
For clarity, here is the full chunk of code that I would like to work:
NSFetchRequest* myRequest = [[NSFetchRequest alloc]
initWithEntityName:#"myEntityName"];
NSSortDescriptor *nameDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"myName"
ascending:YES];
NSSortDescriptor *sectionDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"class"
ascending:YES];
request.sortDescriptors = #[sectionDescriptor, nameDescriptor];
[[NSFetchedResultsController alloc]
initWithFetchRequest:myRequest
managedObjectContext:myContext
sectionNameKeyPath:#"class"
cacheName:nil];

Keep in mind that you can only do this if you are using one entity and its sub entities, because NSFetchRequest needs an entity to search for (you can specify to also search for subentities).
You need to declare a property that identifies the proper type of record you wish to retrieve and regroup. Because class is a runtime value, you need to be able to use an identifier to be used in the underlying store. So, in your particular case, I would use a constant (string or number)property on the parent entity of all the sub entities to identify which subgroup this record belongs to.

You can't do this with a single FRC as NSFetchRequest can only be tied to a single NSManagedObject subclass. Instead you can use multiple fetched results controllers and manually set the section indexes when looking up an object.

Related

NSPredicate fetch based on the count found in relationship

I have a User Class and UserType Class.
User Entity Structure and UserType Structure is listed below.
UserTypes.Type can be employee, student or any other thing.
NSFetchRequest *userRequest=[[NSFetchRequest alloc] initWithEntityName:#"User"];
NSSortDescriptor *sortByName=[[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[req setSortDescriptors:#[sortByName]];
userFetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:userRequest managedObjectContext:self.managedContext sectionNameKeyPath:#"newRelationshilp.type" cacheName:nil];
[userFetchController performFetch:nil];
Now, if the Employee type wants only 5 results it should fetch first 5. If the "Student" type wants only 1 , it should only fetch the first one. So, in short, the showNumberOfEntries should be considered as limit for every type that is fetched.
For solving of you're problem you should use fetchLimit of the NSFetchRequest
[userRequest setFetchLimit:5];

iOS: Sorting and grouping NSFetchedResultsController

I have two tables with the following structure
MainCategory:
name position hasSubCategories
SubCategory:
name position display belongsToMainCategory
Now I want to display all subcategories (where display attribute = YES) grouped by main category. The main category sections should be sorted as defined by position and the subcategories itself (within the section) by their position attribute. (name by the way can be the same for a certain main category...my tables have more attributes but they aren't relevant to understand the problem).
But my order is completely messed up. Why? Here's my code for the FetchedResultsController:
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"SubCategory"];
NSSortDescriptor *mainCatPosition = [[NSSortDescriptor alloc]
initWithKey:#"belongsToMainCategory.position" ascending:YES];
NSSortDescriptor *subCatPosition = [[NSSortDescriptor alloc]
initWithKey:#"position" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObjects:mainCatPosition,subCatPosition,nil];
request.predicate = [NSPredicate predicateWithFormat:#"display = %#", [NSNumber numberWithBool:YES]];
[self.db.managedObjectContext executeFetchRequest:request error:&error];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
managedObjectContext:self.budgetDatabase.managedObjectContext
sectionNameKeyPath:#"belongsToMainCategory.name"
cacheName:nil];
The key path used as sectionNameKeyPath: argument to the fetched results controller must
be the same key that is used in the first sort descriptor or generate the same relative ordering.
The fetched results controller first orders all fetched objects according to the first
sort descriptor and then groups the objects into sections according to the sectionNameKeyPath. Therefore using different key paths (as in your case "belongsToMainCategory.position" vs. "belongsToMainCategory.name") does not work.
This could even cause a runtime error about "out of order sections".

How To Implement Sections with NSFetchedResultsController

I've done some reading up on multiple similar issues and sites and I just cannot make any understanding of this.
I have a NSFetchedResultsController which displays information from a user adding information into a ModalViewController with text fields. The user is presented with a ModalView and they put in some words into the fields, click save and that makes up the single sectioned row of tables in the TableView, which implements the NSFetchedResultsController protocol.
What I would like to do now is:
On one of the text fields, be able to create a new section out of the information provided in that one textField.
My fetchrequest is:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Transaction" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"whoBy.name" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
The attribute I want to do display is occasion.date, which means it is a relationship from the Transaction entity to the Occasion Entity (occasion) and the createdDate is an attribute on the Occasion entity.
I can see the sectionNameKeyPath:nil needs to be updated, but what do I update it to, and also do I have to change any of the datasource methods, titles, etc?
Any help on this would be massively appreciated.
Thanks,
The precise answer depends on what should be displayed as section headers and how the
sections should be sorted.
But generally, you have to add a property to the entity, which is then used as sectionNameKeyPath parameter. In addition, you have to add a first sort descriptor
using the same key (this condition can be relaxed slightly).
For example, if you simply add a string attribute sectionName to the entity and use
that as sectionNameKeyPath and as the first sort descriptor key, then all objects
with the same value for sectionName will be grouped into one section,
and the sections will be sorted by sectionName.
The remaining
sort descriptors are used to sort the objects within each section.
Another example would be to define a separate entity "Section" with a string attribute
"title" and an integer attribute "index", add a to-one relationship "section" from
"Transaction" to "Section", and use section.index as sectionNameKeyPath.
Again, all objects with the same section are grouped together, but the sections are
now sorted by the value of section.index, and you can modify
tableView:titleForHeaderInSection: so that the title is displayed in the section
header instead of the index.
This is quite general, but I hope that it gets you started.

Value of sectionNameKeyPath for many-to-many relationship in Core Data

I have a Core Data model with three entities: Notification, Group and Customer. These are the relationships between them:
A customer belongs to many groups and a group can have many customers.
A notification is sent (belongs) to a group and a group can receive (have) many notifications.
I would like to show all notifications in a UITableView grouped by customer. I have created a NSFetchedResultsController like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.fetchBatchSize = 10;
fetchRequest.predicate = nil;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Notification"
inManagedObjectContext:self.managedObjectContext];
fetchRequest.entity = entity;
// Default sort descriptors are built in a separate custom method
NSArray *sortDescriptors = [self getDefaultSortDescriptorsForEntity:entity];
fetchRequest.sortDescriptors = sortDescriptors;
return [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"group.customers.firstName"
cacheName:nil];
Assuming this is a valid way to retrieve all notifications grouped by customer (I'm not sure about that either) iOS is throwing the following exception:
#"Failed to fetch all Notification objects"
#"Reason: Invalid to many relationship in setPropertiesToFetch: (group.customers.firstName) (NSInvalidArgumentException)"
I have reviewed the relationships again and again to see if something is missing and everything seem to be correct. I can create and delete objects for all entities and links between them are correct as well.
My question is: Is it possible to traverse through several relationships in a sectionNameKeyPath value? How should many-to-many relationships be handled in this kind of scenario?
Yes, you can do this. Just fetch the customers with the FRC and set the sectionNameKeyPath to nil.
Sections
The number of returned results is your number of sections. Populate the section headers with the customer data.
Rows
The number of rows in section would be customer.notifications.count. To populate the row, make sure the notifications are sorted in some way (say, by date) and display them accordingly with something like this:
NSArray *orderedNotifications =
[customerForSection.notifications sortedArrayUsingDescriptors:
#[[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO]]];
Notification *notificationToBeDisplayed =
[orderedNotifications objectAtIndex:indexPath.row];
An alternative - recommended - solution is to change the data model. You could associate the notification with all customers directly. That would have the added advantage, that notifications remain associated with the correct customers even if group memberships change.

Coredata fetching and grouping objects in sections

I am working with a pretty complex object model and am having a bit of trouble with breaking some of my fetches down into sections to display in a tableview.
I have the need to group Meeting managed objects into a few different "pockets" such as project, client and a few others. For several reasons I've decided to implement these as tags that can be associated with a Meeting entity.
So I have created a new Tag entity which has a type and a value and established the relationships between the two:
Meeting <<-->> Tag
If I want to associate a Meeting with a project, I create a Tag with name 'project' and value 'Project Name', then add it to the Meeting entity via the relationship.
I initially thought about using a NSFetchedResultsController but I am getting all sorts of issues all of which I don't really understand.
For instance, this fetch (I'm omitting the unnecessary bits):
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[Meeting entityName] inManagedObjectContext:moc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"tags.name contains[] 'client'"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
[fetch setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:moc
sectionNameKeyPath:#"self.tags.value"
cacheName:nil];
In this particular case the fetch does work but somehow I am getting unexpected results where not only Tags with value client are presented but also ones with value project???
If I change my predicate to tags.name == 'project' I get an exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
I'm probably missing something basic here and admittedly I don't have a lot of experience with predicates but Apple's documentation on the subject leaves a lot to be desired.
As a side question and something that I don't understand either is why do I have to add self to my sectionNameKeyPath in self.tags.value? What does it do?? In this case if I don't add it I get an exception thrown as well:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid to many relationship in setPropertiesToFetch: (tags.value)
Finally, what is the alternative to using fetched results controller in this case? Will it be a bunch fetch requests where I first get each instance of Tag where name == 'project' and iterate through the array to pull the Meeting objects associated with it? It seems highly inefficient but all I can think of at the moment so if you have any other ideas I am very interested in hearing them.
Many thanks in advance for your time,
Rog
The issue is that Meeting has-many tags, so you're going to need to use aggregate operations:
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[Meeting entityName] inManagedObjectContext:moc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.name contains[cd] 'client'"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
[fetch setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:moc
sectionNameKeyPath:#"clientName"
cacheName:nil];
i.e. Give me a list of all Meeting objects where ANY of the tags are of type client, and group them by clientName. For the clientName key path to work, you'll need to implement a transient property:
- (NSString *)clientName {
[self willAccessValueForKey:#"clientName"];
// Set clientName to the value of the first tag with name 'client'
NSString* clientName = #"...";
[self didAccessValueForKey:#"clientName"];
return clientName;
}
If a number of your NSManagedObject subclasses need the clientName property, you can implement it in a common abstract NSManagedObject subclass and make your concrete subclasses inherit from that.
"Apple's documentation on [NSPredicate] leaves a lot to be desired" - totally agreed!
tags.name is not valid because tags is a collection, there's no object, there are (let's say) three of them
i think you want something like "tags contains %#", projectTag but i'm not sure of the syntax. might be "%# IN %#", projectTag, thing.tags

Resources