NSPredicate predicateWithFormat with object that changes over time - ios

I have this working code, but now i need to be able to change the NSPredicate, based on the object used in the predicateWithFormat as follows:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Item" inManagedObjectContext:_context];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"self.atrObj == %#", _currentUser.atrObj.objectID]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortContent = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortContent]];
[fetchRequest setFetchBatchSize:10];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_context sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
I need the fetchedResultsControlelr to use the new _currentUser object when i change the _currentUser object from the app delegate which has this tableviewcontroller a property.
[self.tableViewcontroller setCurrentUser:user];
Thank you!

You can change the predicate anytime but be sure to re-fetch via performFetch. Also from Apple docs:
Important: Important If you are using a cache, you must call deleteCacheWithName: before changing any of the fetch request, its
predicate, or its sort descriptors. You must not reuse the same
fetched results controller for multiple queries unless you set the
cacheName to nil.
Also, from the Apple docs of NSFetchedResultsController:
Modifying the Fetch Request You cannot simply change the fetch request
to modify the results. If you want to change the fetch request, you
must:
If you are using a cache, delete it (using deleteCacheWithName:).
Typically you should not use a cache if you are changing the fetch
request.
Change the fetch request.
Invoke performFetch:.

Whenever the value of currentUser changes, do this (perhaps inside the setter):
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:#"atrObj == %#", self.currentUser.atrObj];
if(![self.fetchedResultsController performFetch:&error]) {
// Handle errors
}

Related

Is it possible to use a sectionKeyPath with NSFetchedResultsController when fetching properties with NSDictionaryResultType?

I've got a fetch request set up to retrieve some properties on 'User' items:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:companyName, firstName, lastName, completeItems, incompleteItems, objectID, nil]];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:SortOptionCompany ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:SortOptionFirstName ascending:YES]]];
[fetchRequest setFetchBatchSize:kBatchSize];
I initialize my NSFetchedResultsController:
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.context
sectionNameKeyPath:#"sectionIdentifier"
cacheName:nil];
The attribute "sectionIdentifier" is a transient property on the User entity. However, when I execute this, I get the message:
"returned nil value for section name key path 'sectionIdentifier'. Object will be placed in unnamed section"
The fetched information isn't grouped into sections as desired. Is this because I need to fetch whole NSManagedObjects for the keyPath to not be nil, or is there a way to use sectionKeyPath when retrieving dictionaries? I've also tried using setPropertiesToGroupBy: with no success.
Since there was no solution that I could find for this issue, I resorted to not using a sectionKeyPath and sorting the data into sections manually after retrieval.

Updating an NSFetchedResultsController with an NSPredicate that uses the current time

I have an application that needs to filter objects based on timestamps. For example, lets say I want to filter an Event to only display Events that are in the past. I want to then display them in a UITableView. I would set up an NSFetchedResultsController like so:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// 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];
// Filter based on only time stamps in the past
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"timeStamp < %#", [NSDate date]];
fetchRequest.predicate = predicate;
[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:nil];
aFetchedResultsController.delegate = self;
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();
}
return _fetchedResultsController;
}
My question is this: what is the best way to update this view so that the filter is based on the current time? My existing solution is to set up a method like this:
- (void)updateFetchedResultsController {
self.fetchedResultsController = nil;
[self.tableView reloadData];
}
Then I call that method on viewWillAppear: or viewDidAppear:. This works unless the user stays on the screen for a while.
I could also use an NSTimer and call updateFetchedResultsController once a minute or so but that causes issues if the user is scrolling through the table. Is there a better way to check if the data has changed? Since the data isn't changing I can't rely on any save events.
You only ever need to change the data on display when an items time is no longer valid. It has a date so you can calculate how long into the future that is and set a timer. You order the data so the next item to expire is always the first in the list.
To finesse, you can check for scrolling when the timer expires and delay the reload until the scroll animations have completed.

Fetching contacts based on tag

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];

how to sort a view-based tableview

I can't manage to sort a view-based tableview. I use an arrayController that control an entity of core data.
I tried to select a column and in the attribute inspector I used as a sort key the attributes name relative to that column and compare: as a selector... when I build and run I click on the header and now display the arrow that change every click, but the nothing happens with the rows. no sorting.
How can I fix it?
I think I'm missing something easy, but I can't get over it.
You may want to consider using an NSFetchedResultsController. From the Apple documentation for NSFetchedResultsController,
You use a fetched results controller to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
The code looks like this where the line that provides the sort descriptor starts with NSSortDescriptor.
- (NSFetchedResultsController *)fetchedResultsController
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"YourEntityName"
inManagedObjectContext:yourManagedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"YourSortKey"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *fetchedObjects = [yourManagedObjectContext executeFetchRequest:fetchRequest
error:&error];
if (fetchedObjects == nil) {
// Handle the error
}
yourFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
return yourFetchedResultsController;
}

Core Data and Table Views

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.

Resources