NSPredicate predicateWithFormat dropping $ symbol from key - objective-c

I am using NSPredicate to filter the contents of an NSArray of NSDictionary objects. Nothing crazy. I want to check three different values in the dictionary for the same search string. Here is the string I am passing to [NSPredicate predicateWithFormat:]:
($ECInstanceLabel CONTAINS[c] filterMe) OR (SubLabel1 CONTAINS[c] filterMe) OR (SubLabel2 CONTAINS[c] filterMe)
When I execute this, a valid instance of a new NSPredicate is created, but when I do [NSArray filteredArrayUsingPredicate:], the application throws an exception.
The exception is:
'NSInvalidArgumentException', reason: 'Can't get value for 'ECInstanceLabel' in bindings { }.'
I think part of the problem is that my format string specifies that the first dictionary key value is $ECInstanceLabel but somehow the $ is getting escaped. I have tried manually escaping this character but then the program crashes when I try to create the predicate, so I don't think the $ symbol is recognized as an escapable character.
Any ideas or something obvious I may be missing?
Thanks in advance.

$var is used in predicates for variables that can be substituted later.
If you have a key containing $ or other special characters then use the %K expansion:
[NSPredicate predicateWithFormat:#"(%K CONTAINS[c] %#) OR (SubLabel1 CONTAINS[c] %#) OR (SubLabel2 CONTAINS[c] %#)",
#"$ECInstanceLabel", searchTerm, searchTerm, searchTerm]
And use the %# format for the search term instead of writing it inline, to avoid
errors if the search term contains special characters.
Of course you can use %# for all keys, but in this case you need it only for
the first key that contains special characters.

Related

Objective-C - wrap attribute value with commas in NSPredicate CONTAINS

Here is the situation. I have some Core Data data and I want to get the data where the value of an attribute wrapped with two commas contains another string.
This is my current code:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type CONTAINS %#", [NSString stringWithFormat:#",%#,",typeBar]];
Simply put, this is what I want to achieve:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#",type, CONTAINS %#", [NSString stringWithFormat:#",%#,",typeBar]];
With the commas around the 'type'. But obviously when I do it like this, it doesn't recognise the attribute anymore. I have tried ,'type', but that doesn't work either.
I have tried this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#",%K, CONTAINS %#", #"type", [NSString stringWithFormat:#",%#,",typeBar]];
But I get a: 'NSInvalidArgumentException', reason: 'Unable to parse the format string ",%K, CONTAINS %#"'
I hope the question was clear enough.
Thanks a lot!
You can't do that. What you're trying is attempting to change the key to be queried to something invalid, hence the parse error.
You can't really do what you want to if you just store a plain type. Presumably your type may be a string list of things?
So, you need to rethink your approach, there are a number of options depending on what your underlying problem is:
Store the text for type with commas at the beginning and end (as well as between items)
Don't use a string, use some other entity (if you have a list of items)
Predicate on the plain type, without the commas, and then filter the results in more detail once you have the items out of Core Data (where predicates are restrictive compared to 'full' code access to the strings)

Puzzled with NSPredicate predicateWithFormat table

both lines of code work but not as I would expect. The first one, using a variable skips only the first item of the table and updates all the remaining items. Using a string literal in the second option doesn't and works the way I want. Why is this happening??
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"myString == %#", date];
OR
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"myString == '5/24/14'"];
To see why this is happening, try logging date:
NSLog(#"%#", date);
You will see that the resulting string (in the console) does not look at all like '5/24/14'! But that (what you see in the console) is the string that you are using in your first code.
So it's all about how the NSDate is covered to an NSString. How to get around this? Use an NSDateFormatter to convert the date to a string in the form you're after!

need help on a simple predicate to match any word in a string to a property

Let's say i want to let user search for my objects using a name property of the objects.
I have no problem if the user only enters one word:
e.g: facial
My predicate will be:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name CONTAINS[cd] %#", word];
But what if user enter more than one word separated by space?
I want to do sth like:
NSArray *words = [query componentsSeparatedByString:#" "];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name CONTAINS[cd] ANY %#", words];
But it doesnt work. Any guidance?
Thanks!
Another way of doing this (and I just learnt this myself as a result of your question) is to use subqueries. Check this SO question for more details. You can use subqueries in the following manner –
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(%#, $str, SELF CONTAINS[cd] $str).#count != 0", words];
NSLog(#"%#", [array filteredArrayUsingPredicate:predicate]);
This seems to work as I've tested it myself but this could also be the arcane & obscure way that Dave has mentioned as it finds no mention in the Predicate Programming Guide.
The format for a SUBQUERY can be found here. It's the same link that you will find in the question linked earlier.
As you mentioned (correctly) in the comment, you can do this by building a compound predicate predicate, although you'll want to use orPredicateWithSubpredicates, and not the and variant.
That really is the best way to do this. There are others, but they rely on more arcane and obscure uses of NSPredicate, and I really recommend going with the build-a-compound-predicate route.

Core Data fetch request with array

I am trying to set a fetch request with a predicate to obtain records in the store whose identifiers attribute match an array of identifiers specified in the predicate e.g.
NSString *predicateString = [NSString stringWithFormat:#"identifier IN %#", employeeIDsArray];
The employeeIDsArray contains a number of NSNumber objects that match IDs in the store. However, I get an error "Unable to parse the format string". This type of predicate works if it is used for filtering an array, but as mentioned, fails for a core data fetch. How should I set the predicate please?
NSPredicate doesn't use formats like NSString does. So that the result of a predicate creation using the pre-generated predicateString won't be a valid predicate.
You have to make it an actual predicate:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"identifier IN %#", employeeIDsArray];
[fetchRequest setPredicate:predicate];
See the documentation for more informations on predicate formats.
When you create that string using stringWithFormat:, that method inserts the array's description (a string describing the contents of the array) where you had %#.
That's not what you want. You don't want to test membership in a string describing the array; you want to test membership in the array. So, don't go through stringWithFormat:—pass the format string and the array to predicateWithFormat: directly.

NSPredicate case-insensitive matching on to-many relationship

I am implementing a search field where the user can type in a string to filter the items displayed in a view. Each object being displayed has a keywords to-many relationship, and I would like to be able to filter the objects based on their keywords. Each keyword object has a name property, so I've set up an NSPredicate to do the filtering that looks like this:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"keywords.name CONTAINS %#", self.searchString];
This works, but the problem is that the search is case-sensitive, so if the keyword has a capital letter but the user types in all lowercase, no matches are found. I've tried the following modification:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"keywords.name CONTAINS[c] %#", self.searchString];
But that doesn't make any difference in the case sensitivity of the matching. Is there a way to do this case-insensitive matching using just a plain predicate? Or will I need to implement some sort of custom accessor on the keyword class, e.g. write a lowercaseName method and match against a lowercased version of the search string instead?
Addendum:
After further exploration, the workaround of adding a custom accessor works OK for manual use of NSPredicate, but does not work at all when using NSFetchRequest with Core Data, which only works when querying attributes defined in the Core Data model.
If I understand you correctly, you want your predicate to be true whenever any keywords name matches the search string. For this you need to test with the ANY keyword like this:
[NSPredicate predicateWithFormat:#"ANY keywords.name CONTAINS[c] %#", ...];
This will search the keywords and return true if any of those keywords name contains your search string.
I believe the answer is:
[NSPredicate predicateWithFormat:#"keywords.name CONTAINS[cd] %#", self.searchString];
String comparisons are by default case and diacritic sensitive. You can modify an operator using the key characters c and d within square braces to specify case and diacritic insensitivity respectively, for example firstName BEGINSWITH[cd] $FIRST_NAME.
Predicate Format String Syntax
If you is trying to catch only the equals names but with insensitive case, I think it is the best solution
[NSPredicate predicateWithFormat:#"ANY keywords.name LIKE[c] %#", ...];
You helped me a lot. Thanks guys!!!
In my case I did:
[NSPredicate predicateWithFormat:#"ANY name LIKE[c] %#", #"teste"];
If you must match the keyword but the search must be case-insensitive then you should use NSPredicate(format: "keywords.name =[c] %#", self.searchString)
LIKE does not work on string literals.
If you want both case insensitive and wildcard, use this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(name like[c] '*%#*')",#"search"]];