I am basically trying to achieve NSSortDescriptor with first name and last name,or with either first name or last name? but with an NSFetchedResultsController. I am reading records from the address book and want to display them just like the Address Book does.
The records will be sorted into sections based on first letter of last name. If there isn't a last name it will sort by first name and if there isn't a first name it will sort by company.
Currently I have
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// read the contact list
NSEntityDescription *entity = [Contact MR_entityDescriptionInContext:[NSManagedObjectContext MR_defaultContext]];
[fetchRequest setEntity:entity];
// sort the results by last name then by first name
NSSortDescriptor *sort1 = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSSortDescriptor *sort2 = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES selector:#selector(caseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:#[sort1, sort2]];
// Fetch all the contacts and group them by the firstLetter in the last name. We defined this method in the CoreData/Human/Contact.h file
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:#"firstLetter" cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Which sorts by last name and then first name. How would I alter this to return the wanted results listed above?
You can just create an instance method on Contact that has that logic:
- (NSString *)sortKey
{
if(self.lastName) {
return [self.lastName substringToIndex:1];
}
if(self.firstName) {
return [self.firstName substringToIndex:1];
}
return [self.companyName substringToIndex:1];
}
Then just have one NSSortDescriptor that uses the key sortKey
Related
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 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];
I'm trying to modify a simple Core Data fetch request for contacts to only look for contacts with a relationship with a certain tag. Contact and Tag are both entities with a many-to-many relationship.
I understand with Core Data I can do this by first fetching the Tag object, and then calling tag.contact, but I don't want to do it this way as the rest of the code is dependent on the fact that the fetchResultsController returns Contact objects, not Tag objects.
If I were to do relational databasing, I could do a simple cross-table query and find all contacts with a certain tag. Is there a simple way I can replicate this via Core Data?
-(NSFetchedResultsController *) fetchedResultsController {
//if fetch controller already exists
if(_fetchedResultsController != nil) {
return _fetchedResultsController;
}
//create a new fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact"
inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//instantiate the fetch controller with the fetch request and sort by last name into sections
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[self managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
//declare delegate of fetch controller as self
_fetchedResultsController.delegate = self;
NSLog(#"fetchResultsController Created");
return _fetchedResultsController;
}
Use NSPredicate.
Lets say you have related Contacts with Tag by name tags and tag entity has property name.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.name = [cd] %#", #"sales"];
[fetchRequest setPredicate:predicate];
Scenario :
I have an expense tracking iOS Application and I am storing expenses from a expense detail view controller into a table view that shows the list of expenses along with the category and amount.
On the top of the tableview, is a UIView with CALENDAR button, a UILabel text showing the date (for example: Oct 23, 2012 (Sun)) and 2 more buttons on the side.
The pressing of the calendar button opens up a custom calendar with the current date and the two buttons are for decrementing and incrementing the date correspondingly.
I want to save the expenses according to the date which is an attribute in my Core data entity "Expense".
Question: Suppose I press the calendar button and choose some random date from there, the table view underneath it, should show that day's particular expenses. What I mean is I want the table view to just show a particular date's expenses and if I press the button for incrementing the date or decrementing the date, the table view should show that day's expenses. I am using NSFetchedResultsController and Core Data in order to save my expenses.
Any thoughts on how I would achieve this? Here's the code for FRC.
-(NSFetchedResultsController *)fetchedResultsController
{
if(_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
AppDelegate * applicationDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext * context = [applicationDelegate managedObjectContext];
NSFetchRequest * request = [[NSFetchRequest alloc]init];
[request setEntity:[NSEntityDescription entityForName:#"Money" inManagedObjectContext:context]];
NSSortDescriptor *sortDescriptor1 =
[[NSSortDescriptor alloc] initWithKey:#"rowNumber"
ascending:YES];
NSArray * descriptors = [NSArray arrayWithObjects:sortDescriptor1, nil];
[request setSortDescriptors: descriptors];
[request setResultType: NSManagedObjectResultType];
[request setIncludesSubentities:YES];
[sortDescriptor1 release];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController.delegate = self;
[request release];
NSError *anyError = nil;
if(![_fetchedResultsController performFetch:&anyError])
{
NSLog(#"error fetching:%#", anyError);
}
return _fetchedResultsController;
}
Thanks guys.
You would have to create a new NSFetchedResultsController with a new NSFetchRequest that has an appropriately set NSPredicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date == %#)", dateToFilterFor];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
// ...
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"SomeCacheName"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
Don't forget to call [self.tableView reloadData]; after assigning the new FRC.
Edit:
You can assign a predicate to an NSFetchRequest which then is assigned to the fetchedResultsController. You can think of the predicate as a filter.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date == %#)", dateToFilterFor];
If you add this to the fetch request by calling [fetchRequest setPredicate:predicate]; you tell the fetched request to only fetch results where to date property of the NSManagedObject matches the date you provide in the predicate. Which is exactly what you want here.
So if you have a method that's called after the user selected a date you could modify it like this:
- (void)userDidSelectDate:(NSDate *)date
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//Here you create the predicate that filters the results to only show the ones with the selected date
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date == %#)", date];
[fetchRequest setPredicate:predicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
//Here you replace the old FRC by this newly created
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//Finally you tell the tableView to reload it's data, it will then ask your NEW FRC for the new data
[self.tableView reloadData];
}
Notice that if you're not using ARC (which you should) you'd have to release the allocated objects appropriately.
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"];