sectionNameKeyPath with custom CoreData model - objective-c

I am working on an app, where I want to display a UITableView filled with student projects. Each project entity has several attributes like title, description, etc. and is also linked with relationships to other entities like students, study course and so on.
What I am trying to do now, is, to use the study courses as sections in which are all the respective projects which are made by students from that study course.
So far, my code looks like this
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:Nil
cacheName:Nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
return _fetchedResultsController;
}
and this is a screenshot of the CoreData model.
I am really new to Obj-C and haven't designed the data model myself, so I'm kind of lost here and would be really glad about any help.

You haven't asked a specific question yet, but I can give you some basic direction. The way you're creating your NSFetchedResultsController looks correct except that you need to do two more things: sort the result by course title and set the sectionNameKeyPath to course title (I'm assuming your Studycourse entity has a title property, but substitute another property as needed).
- (NSFetchedResultsController *)fetchedResultsController
{
...
NSSortDescriptor *courseTitleSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"projecttostudycourse.title" ascending:YES];
NSSortDescriptor *titleSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = #[courseTitleSortDescriptor, titleSortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"projecttostudycourse.title"
cacheName:Nil];
...
}
Note that by providing two sort descriptors, the projects will first be organized by course and then, within each corse, by project title. The other thing worth noting is that key paths use dot notation to follow entity relationships. So the way to reach the course title from a project entity is #"projecttostudycourse.title".

Related

Order when using sectionNameKeyPath in NSFetchedResultsController

In Core Data I have one to many relationship - each layout has many lines:
I am trying to display data inside a table view by grouping into layouts:
Layout Nr 2342 (with orderPosition 1)
Line 1
Line 2
Line …
Layout Nr 2123 (with orderPosition 2)
Line 1
Line …
…
I am using NSFetchedResultsController that I create like this:
NSEntityDescription *entity = [NSEntityDescription entityForName:#“Line” inManagedObjectContext:managedObjectContext];
NSSortDescriptor *orderPositionDescriptor = [[NSSortDescriptor alloc] initWithKey:#"layout.orderPosition" ascending:YES];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setSortDescriptors:#[orderPositionDescriptor]];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"section=='my_section'"]];
[fetchRequest setPredicate:predicate];
self.fetchController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:#"layout.groupNumber"
cacheName:nil];
So I set fetch controller to group in sections by layout groupNumber, I also set sort descriptor to order by layout position (each layout has its own position). As soon as data is added to core data, it is displayed in the table view. Everything works great, till the moment when I try to change NSFetchRequest predicate of the existing NSFetchedResultsController:
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"section=='%#'",section]];
[self.fetchController.fetchRequest setPredicate:predicate];
I set exactly the same section in the predicate as it was already set and the data after performFetch displayed in the table is still the same, but layouts (table sections) changes position in the table and lines are displayed in wrong layouts (basically no oder). I figured out that if I set the first sort descriptor the same as sectionNameKeyPath - "layout.groupNumber", then everything works even if I change predicate. But obviously it is not ordered by orderPosition what I want to achieve. Is there any solution for that? Any help would be appreciated.
If you are using sections in your table view, and sorting your table view data, it is important to understand that the first sort descriptor must be identical to the sectionNameKeyPath.
As such I recommend you change your code to be something similar to this...
NSEntityDescription *entity = [NSEntityDescription entityForName:#“Line” inManagedObjectContext:managedObjectContext];
//new line of code following...
NSSortDescriptor *sectionNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"layout.groupNumber" ascending:YES];
NSSortDescriptor *orderPositionDescriptor = [[NSSortDescriptor alloc] initWithKey:#"layout.orderPosition" ascending:YES];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//altered line of code following...
[fetchRequest setSortDescriptors:#[sectionNameDescriptor, orderPositionDescriptor]];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"section=='my_section'"]];
[fetchRequest setPredicate:predicate];
self.fetchController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:#"layout.groupNumber"
cacheName:nil];
I have read about this but cannot recall where... so no documentation reference I'm sorry.
Does this help?
I found a workaround of my problem. Because each layout group number has unique order position, solution is to group not by groupNumber, but by orderPosition. In that case I can specify orderPosition as the first (and the only) sort descriptor. In case someone else has other better solution, it would be still interesting to hear it.
NSEntityDescription *entity = [NSEntityDescription entityForName:#“Line” inManagedObjectContext:managedObjectContext];
NSSortDescriptor *orderPositionDescriptor = [[NSSortDescriptor alloc] initWithKey:#"layout.orderPosition" ascending:YES];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setSortDescriptors:#[orderPositionDescriptor]];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"section=='my_section'"]];
[fetchRequest setPredicate:predicate];
self.fetchController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:#"layout.orderPosition"
cacheName:nil];

Using an NSPredicate to access self.items

For various reasons (summarized at the end) I am experimenting with using an NSFetchedResultsController to return some NSManagedObjects.
Specifically, a Person has many Cars, modeled using the core data relationship cars. I want to add another method to Person to return the same cars as self.cars, but using a FRC.
I think I am making a basic error with my NSPredicate, which is designed to only find the cars where car.person == self:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Car"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dateAdded" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person == '%#'", self];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
BOOL success = [controller performFetch:&error];
No cars are being returned at all. Is my predicate incorrect?
Side Note - why not just use self.cars?
Like I say this is an experiment. I am hoping to benefit from the persistent cache of an NSFetchedResultsController.
The single quotes in the predicate are wrong, it should be
[NSPredicate predicateWithFormat:#"person == %#", self];
In the example you are fetching "Person" objects. Maybe you want to fetch "Car" objects instead?
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Car"];

Simple NSPredicate #"1 == 0" not filtering fetched results?

What is wrong with this code?
I know this is a silly predicate. But it's just to show that if this was working, it should be filter everything out, right?
Somehow, it's not. I'm getting every Month object in my DB, when I should be getting none.
I guess this tells me that the problem might not be in the predicate?
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Month" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:#"1 == 0"]];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"month_" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:#"month_"
cacheName:#"Root"];
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[_fetchedResultsController sections] count];
}
What I actually wanted to do is,
[NSPredicate predicateWithFormat:#"year.year_ == %d", year]
And I also tried this,
[NSPredicate predicateWithFormat:#"month_ == %d", 1]
Just to see if it works. But nothing.
edit:
At this point, no matter what predicate I set, as long it's a valid, well formatted predicate. It doesn't complaint, and get's me all objects. It's just not filtering anything.
I'm pretty much using boiler plate code from Apple's documentation.
Can anyone help me figure this out?
Thank you!
If using a cache, then the fetchRequest's cache should be deleted, before being reused.
Calling deleteCacheWithName: fixed the problem.

NSFetchedResultsController not sorting on initial fetch

I am having a Core Data problem with NSFetchedResultsController. I have a one to many relationship between a parent and child entity. The array in the childFetchedResults.fetchedObjects property is NOT sorted by number (number is an int32 property in the model). It doesn't seem to matter if I use the MagicalRecord convenience category methods or not.
NSFetchRequest *req = [Child MR_requestAllSortedBy:#"number" ascending:YES withPredicate:[NSPredicate predicateWithFormat:#"parent = %#", self.parent]];
childFetchedResults = [[NSFetchedResultsController alloc] initWithFetchRequest:req managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:nil];
childFetchedResults.delegate = self;
NSError *error;
[childFetchedResults performFetch:&error];
NSLog(#"fetched objects: %#", childFetchedResults.fetchedObjects);
However, if I sort the array of fetched objects using the exact same sort descriptor, it works fine:
NSLog(#"fetched objects: %#", [childFetchedResults.fetchedObjects sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"number" ascending:YES]]]);
I gather you can only use comparators which Core Data can pass on to the SQLite store when specifying sort descriptors for a fetch request. But I feel like that shouldn't matter in this case since sorting by a number has got to be supported by SQLite.
Anyone solved this? I feel like it's a similar issue to the one described here: NSSortDescriptor not being called.
As for MR_requestAllSortedBy, it's in MagicalRecord, here is the implementation:
+ (NSFetchRequest *) MR_requestAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context
{
NSFetchRequest *request = [self MR_requestAllInContext:context];
NSSortDescriptor *sortBy = [[NSSortDescriptor alloc] initWithKey:sortTerm ascending:ascending];
[request setSortDescriptors:[NSArray arrayWithObject:sortBy]];
return request;
}
So this was caused by fetching against a nested MOC with unsaved changes. Either fetching with the parent MOC or saving the nested MOC prior to executing the fetch resolves the problem. Similar to what was going on in this question: NSSortdescriptor ineffective on fetch result from NSManagedContext
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"RemainderDataBase" inManagedObjectContext:[self managedObjectContext]];
NSSortDescriptor *nameSort = [[NSSortDescriptor alloc]initWithKey:#"title" ascending:YES];
NSArray *sortDescriptor = [[NSArray alloc]initWithObjects:nameSort, nil];
fetchRequest.sortDescriptors = sortDescriptor;
NSPredicate *p1=[NSPredicate predicateWithFormat:#"startdate > %#", [NSDate date]];
[fetchRequest setPredicate:p1];
[fetchRequest setEntity:entity];
i think you are looking for the above code..

The fetched object at index 501 has an out of order section name 'JOURNAL OF APPLIED PHYSICS. Objects must be sorted by section name'

I have a database of articles with journal section names. One article is of "Journal of Applied Physics", another is of "JOURNAL OF APPLIED PHYSICS". When I use
[[NSSortDescriptor alloc] initWithKey:#"Journal" ascending:YES elector:#selector(caseInsensitiveCompare:)]
to fetch the data, it gives me the error message.
The fetched object at index 501 has an out of order section name 'JOURNAL OF APPLIED PHYSICS. Objects must be sorted by section name'
I'm already using case insensitive compare, so why would this not work? Help?
=======
code used for fetching data =======
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Article"
inManagedObjectContext:SharedMOC];
[fetchRequest setEntity:entity];
NSSortDescriptor *journalSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Journal"
ascending:ascending
selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:journalSortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *a = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:SharedMOC
sectionNameKeyPath:[self selectedSortSection]
cacheName:cacheName];
My intuition says that instead of:
[fetchRequest setSortDescriptors:[self getSortDescriptor]];
You should write:
[fetchRequest setSortDescriptors:sortDescriptors];