is it possible, to filter the results of a NSFetchedResultsController without a new call to the databaselayer, like I do it with an NSArray with "filteredArrayUsingPredicate"
Thanks
You can set a predicate on the NSFetchRequest that you use to initialize your NSFetchedResultsController. For example:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = <YOUR ENTITY>
fetchRequest.predicate = [NSPredicate predicateWithFormat:<YOUR PREDICATE>];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc sectionNameKeyPath:nil cacheName:nil];
// ...
You shouldn't need to "refresh" the fetched results controller since it should update as changes are made and saved. You may need to use the boilerplate code for using and/or updating a table view with a fetched results controller.
Related
I have an CoreData entity called "MyPhoto" with the attributes :- photoData, timeStamp, folderName. Setting the today Date with Time to timeStamp property when saving photoData to core data. I want sections with folderName and inside the section I want to sort the photos with timeStamp. I am using and NSFetchResultsController to fetch the data & UICollectionView to display it.
Problem is: When I try to insert the new photo, it is not inserting in the correct sorting order but when I relaunch the app, it will show the all the photos in a correct sorting order.
here is the code I am using:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *moc = [[CoreDataManager sharedInstance] managedObjectContext];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyPhoto" inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Sort using the timeStamp property.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"folderName" ascending:YES];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
[fetchRequest setSortDescriptors:#[sortDescriptor, sortDescriptor1]];
// Use the folderName property to group into sections.
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc sectionNameKeyPath:#"folderName" cacheName:#"Root"];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I hope I have explained my problem clearly. Thanks in advance for your help.
I see you are setting the delegate. Have you actually implemented any of the delegate methods?
For starters, you could just call reloadData in controllerDidChangeContent:
I have a simple data model with two entities. A parent entity called Character and a child entity called Statiscis. A Character can have multiple Statistics and each statistic can have only one parent, so the relationship is many to one.
From the view controller that displays the details of a Character I call to a new Table VC to list all the Statistics related to this Character. On this controller I have a nice SIGABRT when I try to build the fetchedResultsController: "Unable to generate SQL for predicate (character == currentCharacter) (problem on RHS)".
When I create the Table VC I send the managedObjectContext and the character displayed on the details VC through two properties (same name) on prepareForSegue, so in the table VC self.currentCharacter hosts an instance of a Character managed object.
#pragma mark - NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Statistic"];
// Stupid predicate :(
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"character == self.currentCharacter"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"statName"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
NSError *error = nil;
// Going to crash
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Core Data error: %#, %#", error, [error localizedDescription]);
abort();
}
return _fetchedResultsController;
}
Do not know how to create the predicate, and I tried unsuccessfully several ways
Perhaps:
[NSPredicate predicateWithFormat:#"character == %#", self.currentCharacter];
You want
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"character == %#", self.currentCharacter];
I currently have a class which has a date object in it. This date object has both time and day in it. All this information gets loaded into a UITableViewCell via a NSFetchedResultsController. I need to sort the dates into sections where each section is the date without the Time. I also need each section to be sorted in itself by time. Here is what my current _fetchedResultsController looks like:
[self.managedObjectContext lock];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Entity" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"due" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
theFetchedResultsController.delegate = sender;
[self.managedObjectContext unlock];
return theFetchedResultsController;
The attribute I need to sort is the Entity.due attribute.
You may want to check out the Core Data Sample called "DateSectionTitles" for more insight, I'm highlighting the basics from it.
To have sections according to date, you need to first sort by a normalized (or simplified) date. Try the following:
Add a transient attribute to your entity in the core data model editor. Ensure that it's type is NSDate and it's transient property is checked. Update your NSManagedObject class files by adding the property as a strong NSDate. Call it, "simplifiedDate". Also add another property to your .h file, call it "primitiveSimplifiedDate". Make it a strong.
In the .m use #dynamic for both the simplifiedDate and primitiveSimplifiedDate. This transient attribute should use return an NSDate that has been normalized to midnight. This level sets everything and allows you to establish the sections. I've used code along the lines of this:
-(NSDate *)simplifiedDate{
// Create and cache the section identifier on demand.
[self willAccessValueForKey:#"simplifiedDate"];
NSDate *tmp = [self primitiveSimplifiedDate];
[self didAccessValueForKey:#"simplifiedDate"];
if (!tmp) {
tmp=[self simplifiedDateForDate: [self due]];
[self setPrimitiveSimplifiedDate: tmp];
}
return tmp;
}
-(NSDate *)simplifiedDateForDate:(NSDate *)date {
if (!date) {
return nil;
}
static NSCalendar *gregorian = nil;
gregorian=[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *newDateComponents=[gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit| NSDayCalendarUnit) fromDate:date];
return [gregorian dateFromComponents:newDateComponents];
}
If you so desired, this could also be an actual attribute that is calculated and stored when the actual date is set.
Next, you will have two sort descriptors. The first sort descriptor will be the above transient attribute, "simplifiedDate". The second attribute will be the "due" attribute. The "simplifiedDate", so long as the NSFetchedResultsController is setup correctly in the next step, will sort everything according to the section.
Finally you need to provide the section key name when you alloc/init the NSFetchedResultsController. In this case it will be "simplifiedDate". Like this:
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"simplifiedDate"cacheName:#"Root"];
Hope this helps.
t
I am making a view just like the iPhone Calendar ListView. I am using core data and getting appointments and grouping them by date.
However just like in the iPhone listview I need to add a blank section for today even if there are no appointments. I cannot figure out how to do this for a section with no appointments since I am doing the sorting before I create the grouping.
How would I add an empty section to the NSFetchedResultsController and have it resorted so that today's date is in the correct spot and not at the end of the list?
- (NSFetchedResultsController *)fetchedResultsController {
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Appointments" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
[fetchRequest setEntity:entity];
//[fetchRequest setIncludesPendingChanges:YES];
// Set the batch size to a suitable number.
//[fetchRequest setFetchBatchSize:20];
// Sort using the date / then time property.
NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSSortDescriptor *sortDescriptorTime = [[NSSortDescriptor alloc] initWithKey:#"start_time" ascending:YES selector:#selector(localizedStandardCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorDate, sortDescriptorTime, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Use the sectionIdentifier property to group into sections.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext] sectionNameKeyPath:#"date" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
return fetchedResultsController;
}
You can't have empty sections with NSFetchedResultsController - that's just the way it is designed at the moment, and I would call it a limitation :)
This problem has been encountered and addressed by a Timothy Armes who has created a class TAFetchedResultsController, which allows for empty sections. It's a replacement for NSFetchedResultsController. It also allows you to sort your sections on fields which aren't the section name (quite handy)
However, you will need to make changes to your Core Data model - it's not quite a drop in replacement.
https://github.com/timothyarmes/TAFetchedResultsController
But it does work well, and will solve your problem if you are willing to re-do your data model.
Hope this helps :)
I'm using the following code to fetch data out of my Core Data graph:
- (void)setupFetchedResultsController
{
// 1 - Decide what Entity you want
NSString *entityName = #"Snag"; // Put your entity name here
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 3 - Filter it if you want
request.predicate = [NSPredicate predicateWithFormat:#"project.id = %#", projectPassedToController.id];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"dateTaken"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
However, I only want to return projects that have the same unique identifier as the one that is being passed to this View Controller (using the ProjectPassedToController variable)
I don't want to filter by the project.name, as this field is editable by the user. I need to filter by the project's unique identifier but i'm not sure how to do this.
Solved this by filtering on the relationship between the project and person.
request.predicate = [NSPredicate predicateWithFormat:#"belongsToProject = %#", _selectedProject];