Filter NSFetchedResultsController for nil value? - iphone

I have a NSFetchedResultsController which is fetching objects from a NSManagedObjectContext.
These objects have a lastOpened property, which is initially nil and is only set when they are viewed.
I want my UITableView to display a list of the most recently opened objects, which I can do by adding a NSSortDescriptor to the NSFetchRequest.
Question:
I don't want to display the objects which have never been studied. (lastOpened == nil) Can I sort this via the same NSFetchRequest? Is there a better way to filter the data?
Thanks.

Absolutely. What you're looking for is an NSPredicate. Basically, it allows you to specify one or more conditions that you want your fetched objects to meet, then only fetches objects that match those conditions.
Building predicates is a slightly tricky proposition, however - you might want to take a look at the Predicate Format String Syntax guide to get started. For your particular case, it's even trickier since I'm not sure there's a representation for the nil value in predicate terms. You can get around this, however, by using some date in the distant past:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"lastOpened > %#",
[NSDate dateWithTimeIntervalSince1970:0]];
You can just check against nil directly in a predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"lastOpened != nil"];
This creates a predicate that only allows objects with a set date (anything after January 1, 1970) to be returned. You'd then add the predicate to the fetch request before you execute it:
// Assuming you have some almost-ready NSFetchRequest *request:
[request setPredicate:predicate];

Try this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"lastOpened != nil"];
... // add sort descriptors etc.
Then add the fetch request to the NSFetchResultsController.

For Swift, you can initialize NSPredicate with a boolean to always return true, effectively a filter that doesn't filter anything
return fetchedResultsController("Terminal", withFilter: NSPredicate(value: true), sortColumns: sortColumns, sortDirections: sortDirections)

Related

Compound Predicate not working

NSMutableArray *arrayOfPredicates=[[NSMutableArray alloc]init];
[self.attendeeListSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop){
NSString *userId=[obj userId];
[arrayOfPredicates addObject:[NSPredicate predicateWithFormat:#"userID == %#",userId]];
}];
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:arrayOfPredicates];
[request setPredicate:compoundPredicate];
I am setting this compound predicate for multiple userIds in an array and i need to get the Users from those User ids using OR.
The above is not working but when I'm hardcoding using
NSPredicate *predicate1=[NSPredicate predicateWithFormat:#"(userID like[cd] %#)
OR (userID like[cd] %#)",#"sheetal2", #"sheetal3"];
[arrayOfPredicates addObject:predicate1];
Now this is working..Can anyboady tell whats the problem with my code..
Thanks
In your first code the predicate uses ==. In the second code the predicate uses LIKE[cd]. Because the userID is an NSString, using LIKE is generally a better way to tell the predicate to compare the values, but the real difference is that the second approach is case and diacritic insensitive and the first approach requires an exact match.

Configuring an NSPredicate with multiple conditions

I'm not certain how one would go about "cascading" several conditions into an NSPredicate.
I'm fairly new to Core Data and not sure if that's the right way to achieve what I am looking to do.
Here is what I am trying to do:
There is a Photo object that has a whereTook relationship to a Location object, which has an inverse of photosTookThere.
The Photo in turn has an NSNumber attribute called isFavourite.
I'd like to configure an NSFetchRequest that will first check that photosTookThere exists and if so, check each resulting Photo object and return only the ones that are set as favourites.
Here is the code so far:
request.entity = [NSEntityDescription entityForName:#"Location" inManagedObjectContext:context];
request.sortDescriptors = [NSArray arrayWithObject[NSSortDescriptorsortDescriptorWithKey:#"locationId" ascending:YES]];
request.predicate = [NSPredicate predicateWithFormat:#"photosTookThere.#count != 0"];
How would I cascade the second condition into the predicate and return the correct results?
Just put each condition inside of parentheses and connect them with AND.
[NSPredicate predicateWithFormat:#"(photosTookThere.#count != 0) AND (isFavourite == %#)", [NSNumber numberWithBool:YES]];
I'd also recommend changing the name of your "whereTook" relationship to "location", and your "photosTookThere" to simply "photos", that's more in line with convention.
I eventually was able to solve this using a subquery:
request.predicate = [NSPredicate predicateWithFormat:
#"(photosTookThere.#count !=0) AND SUBQUERY(photosTookThere, $Photo, $Photo.isFavourite==1).#count >0"
];
You can very simply use AND and OR in your predicate much like you would if you were specifying a "where" condition in an SQL statement. To check for a NULL, which it looks like you want when comparing against "whereTook", you can compare to nil (whereTook = nil).

Filtering against properties of objects in an array with a predicate

I have an array of objects that I'd like to filter using a predicate, but I've not been able to figure out the syntax (or whether it's impossible).
Let's say the object is location and it has properties of latitude and longitude. I've got an array of these called allLocations and I want to produce a new array of locations where the latitude property is greater than 30.
When fetching managed objects you can simply use the property name, but not so with arrays:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"latitude > 30.0"];
returns no matches (despite there being plenty of location objects with latitudes > 30.0.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.latitude > 30.0"];
is no good either, while
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"[SELF latitude] > 30.0"];
and
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"[location latitude] > 30.0"];
throw exceptions.
Am I out of luck?
What you're looking for is the filteredArrayUsingPredicate: method of NSArray. Your first predicate attempt above will work fine when you pass it to that method.
It's worth noting that NSMutableArray uses a different method to achieve a similar effect, filterUsingPredicate:. The MutableArray version alters the receiver, whereas the Array version returns a new Array.
Reference:
NSArray Class Reference
NSMutableArray Class Reference

NSFetchRequest on to-many-relationship excluding some objects does not work as expected

i struggling around NSFetchRequest these days.
My data model look like this:
Post <->> Category
Now i need a fetch request to get all posts where the category.name attribute is not "xxx".
Looking at the documentation for NSFetchRequest is should be:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NONE category.name == %#", categoryName]
But this results in an empty list (the request is used by NSFetchedResultsController in an UITableView.
The docs say:
NONE
Specifies none of the elements in the following expression. For example, NONE children.age < 18. This is logically equivalent to NOT (ANY ...).
If i invert my predicate to
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY category.name == %#", categoryName]
the list contains exactly the objects i want to be excluded from that list.
What am i missing here?
thanks in advance
The predicate form you have is correct.
The simplest explanation is that you have no objects that match the "#"NONE category.name == %#" predicate. That would explain why the inverse works.
I suggest that you:
Log the predicate with NSLog and see what the predicate actually is each time
Fetch all Post objects compare the count of objects returned with the count of objects returned by the "ANY" predicate. If they are the same then...
...log the category.name values to see if any of them do match the "NONE" predicate.
I think that should allow you to find the problem.

Core Data, try to use NSPredicate to filter a toMany relationship set but get the "to-many key not allowed here" error

Here is the model I have:
http://www.girardet.ch/model.png
My goal is to retrieve all the Quotes with these criterias:
belong to a specific theme : the name_en attribute of Themes
order by relevancy
filtered by Authors (with the alias attribute of Authors)
Here is my code:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ThemeEntries" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"relevancy" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// predictate - filter
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"theme.name_en=%# AND quotes.author.alias=%#",#"mytheme", #"myauthor"];
[fetchRequest setPredicate:predicate];
I get the "to-many key not allowed here" error.
If I instead use this predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"theme.name_en=%#, #"mytheme"];
it works well and I can loop over the ThemeEntries that I get and get all my quotes... But it's not filtered by authors.
What can I do to filter by Authors?
Your problem is that the relationship of one of you keypath is a to-many, to-many and the predicate does not know which particular object goes with which.
You have ThemeEntities<<-->>Quotes which is produces a set at each end. The quotes.author.alias keypath says "a set of quotes instances, each linked to author instances which in turn has an alias attribute." The predicate cannot process the set.
You need to use a subquery to jump a to-many keypath. The subquery is essentially a nested predicate which searches a set and returns another set of objects matching the nested predicate. Subqueries are poorly documented but they have the form:
SUBQUERY(collectionName,$collectionElementVariable,expression-with-$collectionElementVariable)
In this case, you are looking for any quote instances that has a author relationship with an alias matching the supplied string. Your predicate would need to look like:
#"theme.name_en=%# AND (0!=SUBQUERY(quotes,$eachQuote,$eachQuote.author.alias=%#).#count)",#"mytheme", #"myauthor"
The subquery says, "Of the set of quotes, take each quote object and test if its author relationship object has an alias attribute matching 'myauthor'. Count the number of quote objects with that match. If the number is non-zero, return TRUE."
You need to use subqueries whenever you walk a keypath across a to-many relationship.