How To Implement Sections with NSFetchedResultsController - ios

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.

Related

NSFetchedResultsController section sorting by class

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.

mapping a subset of core-data entities as a section-key-path

I have a one-to-many relationship between:
Question<<---->Section
A section has many questions. A question has one section.
I now create a NSFetchedResultsController to get all questions seperated by its sections into UITableView-Sections.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"MJUQuestion"];
NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"section.title"
cacheName:nil];
Now all questions get seperated into their corresponding sections which works but is not exactly what i want.
I don't want the questions of all sections, but i want all the questions of a specific subset of sections.
Sections are themselve seperated into categorys:
Section<<----->Category
And i only want the questions of sections that belong to a specific category.
if i would do a NSFetchedResultsController for Sections of that category i would add a predicate like
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"category == %#", category];
[fetchRequest setPredicate:projectPredicate];
But since this is not the case and i want them as section seperator, i'm not sure how to modify my NSFetchRequest properly.
So how do i need to modify my NSFetchRequest that i get the questions of a specific subset of sections and these sections as a UITableView-Section seperator?
Thanks in advance.
You are almost there. You need to update your predicate and use a key path:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"section.category IN %#", categoryArray];
Which will cause the request to check each question and see if its section's category is in the array. This will filter you down to a subset of categories.

NSFetchedResultsController with data from other model

I'm trying to build app with UITableView with data from NSFetchedResultsController. The problem is - I need to show some additional data from other models in table view.
Assume I have 2 models: Students and Groups (I think the relation is obvious: group consists of many students and student can be only in one group). I'm trying to build UITableView with list of groups. I would also like number of students in each group.
(NSFetchedResultsController *) fetchController {
if (!_fetchController) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"GroupModel" inManagedObjectContext:[NSManagedObjectContext MR_defaultContext]];
[fetchRequest setEntity:entity];
NSPredicate *messageInChatPredicate = [GroupModel getMyChatsPredicate];
[fetchRequest setPredicate:messageInChatPredicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
_fetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:#"main"];
_fetchController.delegate = self;
}
return _fetchController;
}
I wonder how I can I add additional information (such as number of students in each group)?
Why not just use group.students.count to get the value from the group entity? You only need one fetchedResultsController and I think you will find Core Data performance is fine.
Your terminology is a bit confusing, usually a model is a core data model created in Xcode and has a number of entities e.g. Group, Student. Sometimes you can use two different models and/or different versions of the models.
Your objects are usually know as Entities and both would belong to the same Core Data model.
EDIT:
To get the count of subgroups, such as girls or boys you can do the following
NSMutableArray *boys = [[NSMutableArray alloc] init];
[NSPredicate predicateWithFormat:#"gender = %#",#"MALE"];
[boys addObjectsFromArray:[group.students filteredArrayUsingPredicate:predicate]];
NSLog(#" boys count is %d", boys.count);
Assuming of course that gender is an attribute of Student.
Create another NSFetchedResultsController that is responsible for the other model (i.e student).
You have basically two options to get additional information such as the number of students in each group.
The first option is to count the students in the Group-Student relation. To serve this request the NSManagedObjectContext will load all students in memory to get a suitable result. Personally, I don't really like this option.
The second option is to store the number of students who belong to a group in a property of the group so that it can be directly add. Yes, you have to maintain the correct number manually. Depending on the amount of data that has to be loaded, this approach is more preferable. Crucially since it's way fas

NSFetchRequest / NSFetchedResultsController returning single property distinct but sorted using another attribute of the entity

I am using an NSFetchedResultsController to display data in a table. The data store contains lots of rows that have a groupID, and multiple entities can share a groupID.
Each entity has an insertion date property as well.
I would like to get a list of distinct groupID ordered by the insertion date property. The following code works, except it is not sorted by the insertion date property. I assume that since that property is not one of the ones being fetched, it is not available for sorting.
And I am using MagicalRecord as well, fwiw.
Is there a way to use other properties of the entity for sorting but not have them as part of the result? Adding in the insertion date to the fetched properties makes distinct superfluous since the dates are all unique as they are.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userID like[c] %#", THEUSERUSERID];
NSFetchRequest *fetchRequest = [AnEntity MR_requestAllWithPredicate:predicate];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:#[#"groupID"]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"inDate" ascending:NO];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
_frController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_rootSavingContext] sectionNameKeyPath:nil cacheName:nil];
Chadbag ... I had the same problem. I researched the setPropertiesToFetch and found out it requires an NSArray of NSPropertyDescriptions ... not just the attribute name.
I think if you REMOVE this:
[fetchRequest setPropertiesToFetch:#[#"groupID"]];
and ADD this:
NSDictionary *entityProperties = [entity propertiesByName];
NSPropertyDescription *propDescription = [entityProperties objectForKey:#"groupID"];
NSArray *propArray = [NSArray arrayWithObject:propDescription];
[fetchRequest setPropertiesToFetch:propArray];
That should work for you ... it did for me!

Duplicate Entries for A Core Data Table View Controller and How To Handle This With NSFetchedResultsController

I have a simple application with the following premise and a few minor issues:
I have 4 Tabs, each with a table view displaying different information and each with a plus button going modally to the same view controller, allowing users to add in information to the app.
The user adds a Name, Title, Date and Amount in the view controller and when they press save, it gets saved to the Core Data table view.
The first tab displays ALL for bits of information above (Name, Title, Date, Amount) in a custom table cell with labels, etc. That works well.
The second tab displays only information on the Name, the third tab displays only the Titles. This way, a user sees everything in the first Tab, but only the "Names" in the second tab which they can go select to gather information about that name.
The app is working well when there are only one entry per name (or date or title), but the moment I add a second entry, it duplicates in the Name tab.
For example, if I have an entry for John Smith with only a single entry, it will display that in the Name tab with one entry and when I go in, it shows me only transactions that have John's name attached to it (1 in this case). All good.
However, if I add another Entry for John (spelt exactly the same), it treats it as a separate entry and suddenly, the Name tab now has 2 John's.
I have not put any unique IDs in the attributes of the Core Data Model, but do I have to do this, or could I just use a predicate to ask "if this already exists, don't display it twice" in the Name Tab Bar?
Any assistance would be massively appreciated!
Edit:
Here is the fetchRequest for the Name tab:
- (NSFetchedResultsController *)fetchedResultsController
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Transaction" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"whoBy.name" ascending:NO];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
As we can see, there are no predicates or anything similar. What I want is for a simple check to see "if name exists, don't add it twice" to the Name table view. I hope this makes sense?
The entry duplicates because in the Names tab you're querying the Transactions table and then getting the name of the person who did that transaction. Because John has two transactions, you get his name twice.
Try something like this:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
Like this you should get every name (person) only once (assuming the relationship between Names and Transactions is one-to-many).
EDIT: Every time you create a Transaction, there are two things that can happen with the Person:
it can be a new Person, and you should create a new entity; or,
it is a new Person.
The logic is like this, and you'll have to figure out the code:
When the user is adding a Transaction, you'll need to check if it's for a new person or an existing one — if it's an existing one, maybe he can choose it from a list of Persons (and when he selects a person, you get its NSManagedObjectID). If it's a new one, you create it on the spot.
After creating the Transaction object, you need to add the Person object to its relationship. If I saw correctly, you have a whoBy relationship in the Transaction object. Set it to the Person object you got in step 1.
Set all the other fields of the Transaction object (amount, etc.).
Save the NSManagedObjectContext when you're done with all that.
What you're doing in your code at the moment, is create a new Person for every Transaction, which is why you see multiple entries of the same name.
Yes you can tell CoreData that you want only distinct values by setting:
NSFetchRequest *request;
request.propertiesToFetch = [NSArray arrayWithObject:#"Name"];
request.returnsDistinctResults = YES;
request.resultType = NSDictionaryResultType;
...
But if you don't want 2 Johns in database then it is better practice that you don't put them there than to filter results. So when you insert data check if you already have that entry and If you do don't insert it (you can just update that record)...

Resources