I currently have a CoreData entity that has a name and date attributes, and I would like to create a NSFetchedResultsController that returns the results sectioned by name sorted by date descending (both the sections and its contents), and if possible, only one entry per section. I prefer not to use NSDictionaryResultType.
Lets say I have the following entries:
Name | Date (year/month/day)
-----+----------------------
Anne | 2014/01/16
John | 2014/01/17
John | 2014/01/15
Nick | 2014/01/13
Nick | 2014/01/10
For the above data I wish to obtain only the following results:
Section | Entry Date
--------+-----------
John | 2014/01/17
Anne | 2014/01/16
Nick | 2014/01/13
How do I create the NSFetchedResultsControllerto obtain only the data and in the order listed above?
As of now I have the following code:
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"my predicate" argumentArray:...]];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"name"
cacheName:nil];
The above code sections the data by name, but each section doesn't have its entries sorted by dateAND it has entries that are not from that name!
The result that I want is the table showing unique name sorted by date like showed in the beginning of the post.
Update: I've been unable to do this, so I switched to result type dictionary and used group by for now, I will leave the question open in the hopes someone knows how to do it.
There is no way (that I am aware of) to limit each section to one result in the NSFetchedResultsController.
But, you might not have to do this. You could probably get away with not displaying everything that is not in the first section.
Also, you should be aware that you should not section the request by using any other key than the key used in the primary sort descriptor
If you are using this with a tableView you could try something like this:
FRC creation
NSSortDescriptor *sdName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *sdDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
// sort by name, then sort by date
[fetchRequest setSortDescriptors:#[sdName, sdDate]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"name"
cacheName:nil];
// fetch...
and the tableViewDataSource:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSFetchedResultsController *fetchedResultsController = [self fetchedResultsController];
return [fetchedResultsController.sections count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1; // only display one result for each section
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSFetchedResultsController *fetchedResultsController = [self fetchedResultsController];
YourObject *object = [fetchedResultsController objectAtIndexPath:indexPath];
... configure cell ...
Depending on how many objects you have per person this might not be feasible. If there are a lot of entries you won't display I would recommend to normalize the core data model. E.g.:
You then would display a list of Persons. But maybe this normalization would be a good idea regardless of the number of objects per name. Fetching more objects than are displayed smells fishy to me.
Here is a snippet of some code, in regards to my comment
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"EntityThingO"];
NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSSortDescriptor *sortDescriptorName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
fetchRequest.sortDescriptors = #[sortDescriptorDate , sortDescriptorName];
[fetchRequest setFetchBatchSize:10];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"date"
cacheName:nil];
Related
I'm trying to fetch two entities eg: Student with email attribute and Professor, both of have same parent entity eg: Person with attributes entityId, firstName and lastName i want to generate them in two sections using NSFetchedResultsController.
Here is a part from getter for fetchedResultsController
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setFetchBatchSize:20];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSEntityDescription *description = [NSEntityDescription entityForName:#"Person"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:description];
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES];
NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:YES];
[fetchRequest setSortDescriptors:#[firstNameDescriptor, lastNameDescriptor]];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"entityId"
cacheName:nil];
All Students have the same entityId and all Professors too
In tableView I have two prototype cells one for Student and another for Professor.
I get two sections as expected but students are in different sections, i have printed all objects from fetchedResultsController in console like this, po [self.fetchedResultsController objectAtIndexPath: [NSIndexPath indexPathForItem:1 inSection:1]] all professors are printed with fault:
<Professor: 0x6080000befc0> (entity: Professor; id: 0xd0000000001c0002 <x-coredata://03A3ECAD-CCA7-424E-86F9-258D25372BA1/Professor/p7> ; data: <fault>)
I have forced the fetch request to return full objects using [request setReturnsObjectsAsFaults:NO] but it had no effect.
Why is it happening so?
To avoid "data fault" issue you should set this field of NSFetchRequest:
request.returnsObjectsAsFaults = NO;
To separate students from professors in two sections you can use multiple NSFetchedResultsControllers, as described here:
https://stackoverflow.com/a/2309855/1689376
To avoid duplication of your code, just create a method like this and call it twice:
- (NSFetchedResultsController) createFetchedResultsController: (NSString *) entityName {
//move your code here
}
All I want to know is how to set NSSortDescriptor to NSFetchRequest.
Can't we perform sort by comparator in CoreData?
Any other work around?
Any suggestions to change my model to facilitate that?
BTW, I will have no clue in advance what type of recordValue I will be adding, I will have variable number of recordvalue in all the possible combinations.
I have core data model as shown and data is stored as given below
(When I want to sort by dateValue, the dataValue of DOB should only be considered for sorting)
id stringValue<NSString> dateValue<NSDate> record
name Alex - record1<ZCMORecord>
DOB - 10/10/1990 record1<ZCMORecord>
name Anto - record2<ZCMORecord>
DOB - 05/05/1990 record2<ZCMORecord>
name Max - record3<ZCMORecord>
DOB - 10/10/1990 record3<ZCMORecord>
name Mary - record4<ZCMORecord>
DOB - 01/01/1990 record4<ZCMORecord>
This is how you sort a NSFetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"ZCMORecordValue" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortStringValue = [[NSSortDescriptor alloc]
initWithKey:#"stringValue" ascending:YES];
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc]
initWithKey:#"dateValue" ascending:YES];
[fetchRequest setSortDescriptors:#[sortStringValue, sortDate]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
This will sort first by the name and then by the date. Invert the order of the sort descriptors if you want to sort by the date and then by the name.
I have problem with this adnnotation.I have downloaded several tutorials e.g.: http://www.raywenderlich.com/14742/core-data-on-ios-5-tutorial-how-to-work-with-relations-and-predicates and in this sample code "fault fulfilled" message is visible too. My question is:
It is possible to create and fetch data from Core Data without this
alert?
Does a very critical alert in Core Data?
My database looks like this:
And problem occur when I want to fetch data from "FailedBankDetails" table, e.g. "closeDate".
First I fetch all rows from "FailedBankInfo" table (code above):
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FailedBankInfo" inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"name" ascending:NO];
[fetchRequest setEntity:entity];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
Next I want to display data using table cell:
if ([[_fetchedResultsController objectAtIndexPath:indexPath] isKindOfClass:[FailedBankInfo class]]) {
FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = info.name;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#, %#", info.city, info.details.closeDate];
}
I my opinion accessing to info.details.closeDate occur "fault fulfilled" alert, because when I change this e.g. to info.name this alert not appear.
There is other way to access to "closeDate" field related with a specific "FailedBankInfo" object?
I'm tired of solving this problem, please send me some good advice.
This is my alert:
2014-02-16 09:05:45.801 FailedBankCD[57158:70b] CoreData: annotation: fault fulfilled from database for : 0x8ba2d60
What you are seeing is normal - core data logging is enabled for your app (this will be under the arguments section of your debug scheme).
A "fault", despite the name, is not a problem. It's a core data optimisation whereby full objects are not retrieved from the database until their properties are accessed, at which point the "fault" is "fulfilled".
They didn't have beginners in mind when choosing the language, I think.
I am using NSSortDescriptors to order core data objects and create table view sections depending on a date attribute. Using this kind of SortDescriptor, the table view sections are ordered as expected, but the rows inside the sections are also ordered by the same date attribute. Is there a way to have another ordering system inside each section? I guess the main problem is that core data stores date objects with date+time values, that way there are no objects with exactly the same date value. But I would need to order the objects inside the same section based on another attribute.
Thank you.
And here is my code for the NSFetchedResultsController:
-(NSFetchedResultsController*)fetchedResultsController{
if (_fetchedResultsController != nil){
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSManagedObjectContext *context = self.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"itemDate" ascending:YES];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc]initWithKey:#"itemName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor,sortDescriptor1, nil];
fetchRequest.sortDescriptors = sortDescriptors;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"sectionIdentifier" cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
EDITED QUESTION
This is the piece of code added to the cellForRowAtIndexPath method:
int indexpathsection = indexPath.section;
[self sortedSectionForIndex:indexpathsection];
NSLog(#"INDEXPATH= %ld", (long)indexPath.section);
And this is the method proposed y Marcus:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"priority" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSLog(#"INDEX********* = %ld", (long)index);
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted;
}
The NSFetchedResultsController is meant for the sort order to be controlled via a single sort or set of sorts. It is not intended to do what you are trying to do.
But you can do it, it is just not as clean.
First, the discussion of sorting a section. This does not require you to write your own sorting algorithm:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"MyKey" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted
}
With that method, you can ask for a section to be sorted whenever you want to retrieve an object. However that is inefficient as you are sorting every time you want to touch an object which in a UITableViewController is a LOT.
Instead, take this example and integrate it with your NSFetchedResultsController and its delegate methods so that you are storing the sorted arrays and keeping them in sync. That way you are only doing the sort when the data changes instead of on each method call.
Update
The sort code I provided to you does not sort what is inside of the NSFetchedResultsController. It takes what is in the section, sorts it and returns you the sorted array. So you need to use what it returns:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *sorted = [self sortedSectionForIndex:[indexPath section]];
id object = [sorted objectAtIndex:[indexPath row]];
id cell = ...; //Now get your cell reference
//Now update your cell with the data from object
return cell;
}
You can normalize the time component of your attribute in the awakeFromFetchmethod of your NSManagedObject.
- (void) awakeFromFetch {
[super awakeFromFetch];
NSDateComponents* comps = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:self.itemDate];
self.itemDate = [[NSCalendar currentCalendar] dateFromComponents:comps];
}
In addition, looking at your code, you have to tell the NSFetchedResultsController on what it should base the section breaks. You do this by providing a keyPath for the section breaks when you initialize it. For your case, instead of using sectionNameKeyPath:#"sectionIdentifier", use the first sort key:
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"itemDate" cacheName:nil];
I have an NSManagedObject for the sections in the grouped UITableView.
This object has the attributes "name" and "createdAt".
I want to use "name" in te UI for the section titles, but sorted by "createdAt".
According to the documentation the first sortDescriptor key has to be also the sectionNameKeyPath of the NSFetchedResultsController.
I suggested using two sortDescriptors, but it doesn't work. The sections are still sorted by name.
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:[CoreDataHelper instance].managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortName, sortDate, nil]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[CoreDataHelper instance].managedObjectContext sectionNameKeyPath:#"name"
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
return _fetchedResultsController;
}
A fetched results controller (FRC) uses only the first sort descriptor to group (and sort) the objects into sections. A second sort descriptor can be added to sort the objects within each section.
Also, the key path of the sort descriptor must be the same as the sectionNameKeyPath of the FRC (or at least generate the same relative ordering).
See also Creating a Fetched Results Controller in the “Core Data Programming Guide”:
... In this example you add one more NSSortDescriptor instance to the
NSFetchRequest instance. You set the same key from that new sort
descriptor as the sectionNameKeyPath on the initialization of the
NSFetchedResultsController. The fetched results controller uses this
initial sort controller to break apart the data into multiple sections
and therefore requires that the keys match.
In your case, you can proceed as follows:
Use createdAt as sectionNameKeyPath and in the first sort descriptor.
Modify the titleForHeaderInSection delegate function to return the name property instead of createdAt:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
return [[[sectionInfo objects] objectAtIndex:0] name];
}
Note: If you have multiple objects with the same name but different createAt values, these will be grouped into different sections with the above approach. I don't know if that is a problem for you.
You're almost there. You need to define the sectionNameKeypath in your initWithFetchRequest call.
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDate]];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[CoreDataHelper instance] sectionNameKeyPath:#"name"];