NSPredicate and CoreData? - objective-c

The query works fine if directly added to a predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"author == %#", author];
[request setPredicate:predicate];
[self.managedObjectContext executeFetchRequest:request error:nil];
The query doesn't work if created and then passed to a predicate
Is there a solution? I rather not pass the predicate itself to the method
Author is a subclass of NSManagedObject
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "%#"'
[self fetchObjectsWithQuery:[NSString stringWithFormat:#"author == %#", author];
- (void)fetchObjectsWithQuery:(NSString *)query
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%#", query];
[request setPredicate:predicate];
[self.managedObjectContext executeFetchRequest:request error:nil];
}

Format strings work differently in
NSString *query = [NSString stringWithFormat:#"author == %#", author] // (1)
and
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"author == %#", author]
In particular the placeholders "%#" and "%K" have different meanings.
(1) produces a string of the form
"author == <textual description of author object>"
which you cannot use in
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%#", query];
So you cannot pre-format predicates as strings. Another example to demonstrate the problem:
[NSPredicate predicateWithFormat:#"author == nil"]
works, but
[NSPredicate predicateWithFormat:"%#", #"author == nil"]
does not.

There's no good reason not to create and pass around NSPredicate objects. There are ways to do exactly what you want to do, but they are either less expressive than just using predicates or will require you to duplicate the logic underlying NSPredicate.

Related

IN operator with NSPredicate and SBElementArray

Does the IN operator work for filtering SBElementArrays? I have been trying to use it but it always returns a NULL array.
My code (hexArray will typically have more elements):
SBElementArray *musicTracks = [libraryPlaylist fileTracks];
hexArray = [NSArray arrayWithObject: #"3802BF81BD1DAB10"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %K IN %#",#"persistentID",hexArray];
NSLog(#"%#", [[musicTracks filteredArrayUsingPredicate:predicate] valueForKey:#"persistentID"]);
NSLog(#"%#", hexArray);
NSLog(#"%#", predicate);
Output:
2013-05-26 12:59:29.907 test[1226:403] (null)
2013-05-26 12:59:29.907 test[1226:403] (3802BF81BD1DAB10)
2013-05-26 12:59:29.908 test[1226:403] ANY persistentID IN {"3802BF81BD1DAB10"}
I have tried setting the predicate to:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %K == %#",#"persistentID",hexArray];
Output:
2013-05-26 13:03:04.629 test[1258:403] (3802BF81BD1DAB10)
2013-05-26 13:03:04.630 test[1258:403] (3802BF81BD1DAB10)
2013-05-26 13:03:04.630 test[1258:403] ANY persistentID == {"3802BF81BD1DAB10"}
And this works fine. But I would like the IN functionality.
Instead of doing
persistentID IN ('abc', 'abc', 'abc', ...)
you can do
persistentID == 'abc' OR persistentID == 'abc' OR ...
It seems to work pretty fast.
NSMutableArray *subPredicates = [NSMutableArray arrayWithCapacity:persistentIDs.count];
for (NSNumber *persistentID in persistentIDs) {
[subPredicates addObject:pred(#"persistentID == %#", persistentID.hexValue)];
}
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
[tracks filterUsingPredicate:predicate];
NSLog(#"%ld", tracks.count);
Try using CONTAINS[c]
Ex:-
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %# CONTAINS[c] %k",hexArray, #"persistentID"];
I ended up just looping through all the elements of hexArray and using an equality predicate on each pass. Probably not the most efficient, but it works.
for (NSString *hexID in hexArray){
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"persistentID == %#",hexID];
iTunesTrack *track = [[musicTracks filteredArrayUsingPredicate:predicate] objectAtIndex:0];
[track duplicateTo:playlist];
}
Your predicate should be %K IN %# (without the ANY), if I understand your intention correctly (get all the tracks that have one of the IDs in the array).
For some reason, this doesn't work with SBElementArray, but you could simply convert it to a regular NSArray before applying the predicate (an NSSet should work too, and might be more efficient):
SBElementArray *musicTracks = [libraryPlaylist fileTracks];
NSArray *musicTracksArray = [NSArray arrayWithArray:musicTracks];
NSArray *hexArray = [NSArray arrayWithObjects: #"CE24B292556DB1BA", #"CE24B292556DB1F0", #"CE24B292556DB1C4", nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"persistentID", hexArray];
NSLog(#"%#", [[musicTracksArray filteredArrayUsingPredicate:predicate] valueForKey:#"persistentID"]);
Scripting Bridge technically supports the IN operator, in that it will construct a properly-formed Apple event for it, but most applications don't understand it. The best workaround is the chained OR tests as suggested by NSAddict.

NSCompoundPredicate

I'm trying to filter a UITableView's data using a UISearchDisplayController and NSCompoundPredicate. I have a custom cell with 3 UILabels that I want to all be filtered within the search, hence the NSCompoundPredicate.
// Filter the array using NSPredicate(s)
NSPredicate *predicateName = [NSPredicate predicateWithFormat:#"SELF.productName contains[c] %#", searchText];
NSPredicate *predicateManufacturer = [NSPredicate predicateWithFormat:#"SELF.productManufacturer contains[c] %#", searchText];
NSPredicate *predicateNumber = [NSPredicate predicateWithFormat:#"SELF.numberOfDocuments contains[c] %#",searchText];
// Add the predicates to the NSArray
NSArray *subPredicates = [[NSArray alloc] initWithObjects:predicateName, predicateManufacturer, predicateNumber, nil];
NSCompoundPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
However, when I do this, the compiler warns me:
Incompatible pointer types initializing 'NSCompoundPredicate *_strong'
with an expression of type 'NSPredicate *'
Every example I've seen online does this exact same thing, so I'm confused. The NSCompoundPredicate orPredicateWithSubpredicates: method takes an (NSArray *) in the last parameter, so I'm REALLY confused.
What's wrong?
First of all, using "contains" is very slow, consider mayber "beginswith"?
Second, what you want is:
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Three, you could've just done something like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.productName beginswith[cd] %# OR SELF.productManufacturer contains[cd] %#", searchText, searchText];
orPredicateWithSubpredicates: is defined to return an NSPredicate*. You should be able to change your last line of code to:
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
... and still have all of the compoundPredicates applied.
Here's an useful method i created based on the answers above (which i thank very much!)
It allows to create an NSPredicate dynamically, by sending an array of filter items and a string which represents the search criteria.
In the original case, the search criteria changes, so it should be an array instead of a string. But it may be helpful anyway
- (NSPredicate *)dynamicPredicate:(NSArray *)array withSearchCriteria:(NSString *)searchCriteria
{
NSArray *subPredicates = [[NSArray alloc] init];
NSMutableArray *subPredicatesAux = [[NSMutableArray alloc] init];
NSPredicate *predicate;
for( int i=0; i<array.count; i++ )
{
predicate = [NSPredicate predicateWithFormat:searchCriteria, array[i]];
[subPredicatesAux addObject:predicate];
}
subPredicates = [subPredicatesAux copy];
return [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
}

Performing a search on an NSArray from a UISearchBar

I'm trying to perform a search on some results from a JSON feed that have been loaded into an NSArray called tableData and then displayed in a UITable.
The searchResults NSArray has been declared the .h file. The problem is that at the moment searchResults are empty and output nothing to the console. I'm not sure why...
I'm wondering if there's something missing from the
searchResults = [tableData filteredArrayUsingPredicate:resultPredicate];
line of code below
thanks for any help.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSPredicate *resultPredicate = [NSPredicate
predicateWithFormat:#"SELF contains[cd] %#",
searchText];
searchResults = [tableData filteredArrayUsingPredicate:resultPredicate];
NSLog(#"searchResults: %#", searchResults);
NSLog(#"tableData results: %#", tableData);
}
If you are looking for a field called 'cat' in your Dictionary, instead of having the predicate look in SELF, it should look in SELF.cat.
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"SELF.cat CONTAINS[cd] %#", searchText];
If you still need to check other fields contained within the object, you can create a compound predicate. Details on Predicates can be found in Apple's Predicate Programming Guide.
Try to replace [cd] -> [c]:
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"self.youProperty contains[c] %#", searchString];
like[cd] means “case- and diacritic-insensitive like.”

NSPredicate with keypath on the right side

I need to use a NSPredicate like this:
NSString *authorNameAndLastName = #"Name LastName";
[NSPredicate predicateWithFormat:#"%# beginswith[cd] %K",authorNameAndLastName,#"name"]
but it doesn't work and I receive this exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate ("Name LastName" BEGINSWITH[cd] name).'
After a lot of try I suppose I can't put a keypath (name) in right side of the predicate expression.
It's right?
There's a workaround to obtain what I need in this specific case?
This only would work with arrays and sets. Core Data doesn't support this type of predicate.
Have you tried using [NSPredicate predicateWithBlock:]?
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(YourObject *object, NSDictionary *bindings) {
NSComparisonResult result = [authorNameAndLastName object.name options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [object.name length])];
if (result == NSOrderedSame) {
return YES;
}
return NO;
}];

NSPredicate concatenating attributes

I m trying to figure out how to concatenate attribute names. I have a county and a district attribute that I want to query like
[NSPredicate predicateWithFormat:#"county + district contains[cd] %#",searchBar.text]
gives me unimplemented SQL generation for predicate error. and I am not sure how to implement NSPredicate.
Thanks
This should give you an idea of how to do some more complicated searching. It will match queries for "county district", "district county", etc.
NSArray *searchTerms = [searchBar.text componentsSeparatedByString:#" "];
NSString *predicateFormat = #"(county contains[cd] %#) OR (district contains[cd] %#)";
NSPredicate *predicate;
if ([searchTerms count] == 1) {
NSString *term = [searchTerms objectAtIndex:0];
predicate = [NSPredicate predicateWithFormat:predicateFormat, term, term];
} else {
NSMutableArray *subPredicates = [NSMutableArray array];
for (NSString *term in searchTerms) {
NSPredicate *p = [NSPredicate predicateWithFormat:predicateFormat, term, term];
[subPredicates addObject:p];
}
predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
}
[fetchRequest setPredicate:predicate];
See Cocoa Is My Girlfriend: Adding iTunes-style search to your Core Data application for more explanation.
I ended up implementing another field as concatenating the two variables(district+country) and perform the query on that variable.
I did something similar and concluded that like cekisakurek, the best method was to concatenate the fields into a common field (more relevant for first/last name)
- (NSString *)fullName {
return [NSString stringWithFormat:#"%# %#", self.firstName, self.lastName];
}
and then filtered on this field using 'contains[cd]'
[NSPredicate predicateWithFormat:#"fullName contains[cd] %#", self.searchBar.text];
[NSPredicate predicateWithFormat:#"county contains[cd] %# AND district contains[cd] %#",searchBar.text,searchBar.text];
Just try the above lines of code, it would helps you.