NSPredicate multiple field search - iphone

I'm trying to search multiple fields. Something like this:
[NSPredicate predicateWithFormat:#"(name title contains[cd] %#) AND (title contains[cd] %#", self.searchBar.text];
A search is made on both the name field or the title field.
Also, if anyone knows what a wildcard search would look like I'd appreciate that too.
I tried:
[NSPredicate predicateWithFormat:#"* contains[cd] %#", self.searchBar.text];
my error code:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "(name contains[cd] %#) OR (title contains[cd] %#"'

First off, you must specify all the properties to examine, since there is no fixed or universal list of properties, and in Objective-C there is no real semantic distinction between a property and any other method.
Second off, to examine multiple properties to see if they contain a search string, you should use OR, not AND, since your search is satisfied if any of the properties match, not all.
Otherwise, the syntax you have appears correct (I'm assuming name title in the first subpredicate is meant to be just name.)

Not sure if anyone still reads this post, but I found your problem. You are missing a closing ")" in the second predicate. Here is the correct code:
[NSPredicate predicateWithFormat:#"(name title contains[cd] %#) AND (title contains[cd] %#)", self.searchBar.text];
Edit: After testing this I noticed that two format specifiers worked fine with ONE format argument (see code above). When I added a third predicate and format specifier (with only one format argument) it crashed. Either way, you should always have the same amount of format specifiers and format arguments.

Related

NSPredicate with to-many to-many relationships

I am having some trouble writing a predicate for my NSFetchedResultsController to get all Modifier from a table where where Exercise and Modifier contain the same Exercise_Modifier
The model look like this: Exercise <->>>Exercise_Modifier<<<->Modifier
I've tried using ANY and ALL keywords but cannot seem to get to the answer. I've tried this along the line of this:
fetchRequest.predicate = NSPredicate(format: "ALL exercise_modifiers == %#", exercise!.modifiers!)
where exercise_modifiers is the relationship from Modifier <->>>Exercise_Modifier and exercise!.modifiers! are the Exercise_Modifier for the current exercise I have. Does anyone know where I might be missing something?

How to use Dynamic Multiple NSPredicate from NSArray in Core data

I am fairly new to core data. I have database which contains articles with different categories. I want to fetch data from multiple categories, which can be selected by user on the fly. Now I have NSArray which contains selected multiple categories (count from 1 to n) I want to add predicate for selected categories but I'm not able to do so. as per my knowledge i have added for loop for adding categories in predicate but it is not working.
for i in 0...(catArray?.count)!-1 {
let str = String(format:"catId = %#",(catArray?.object(at: i) as? NSNumber)!)
predicateString = predicateString+str
}
let dP = NSPredicate(format:"%#",predicateString)
fetchedRequest.predicate=dP;
but app is crashing while fetching request.
Is there any other way to do it?
Please help me with this.
Any help is much appreciated.
To select objects which have any category from the given array
a simple predicate is sufficient:
NSPredicate(format: "catId IN %#", catArray)
Remark: Never use String(format:) to build predicates dynamically,
because almost surely the quoting and escaping will be wrong.
Use only NSPredicate(format:) and, if necessary, NSCompoundPredicate.

accessing values using multiple relationships with NSPredicate

here is my core data model:
Locations < --- >> ThemesList << --- > Themes
The entites have the following attributes:
Locations
- Property: Name
- Relationship: ThemesList
ThemesList
- Relationship: Locations
- Relationship: Themes
Themes
- Property: Name
- Relationship: Locations
I am running a fetch on entity Locations and I want to only pull values where the name property in Themes is equal to a particular value. Based on what I've read, I need to do a subquery. I've tried something along the lines of the code below, but I always receive the error of Unable to parse the format string
[NSString stringWithFormat:#"SUBQUERY(ThemesList, $theThemes, $theThemes.Themes.Name LIKE %#)", #"a theme name"];
Any ideas on how I can accomplish this - what am I doing wrong?
Thanks
As a rule of thumb, you should fetch the objects of the entity you are testing with the predicate instead of starting with another entity altogether and walking a relationship inside the predicate. Walking relationships, especially to-many relationships inside of predicates is computationally intensive and slow.
Instead, I would recommend running the fetch against the Theme entity and then just ask the returned Theme objects for their associated Locations objects.
Your predicate would simplify to just:
NSPredicate *p = [NSPredicate predicateWithFormat:#"Themes.Name LIKE %#", #"a theme name"];
Then use a collection operator to find the location objects:
NSSet *locations=[fetchedThemes valueForKeyPath:#"#distinctUnionOfSets.locations"];
Your problem is this: $theThemes.Themes.Name. The parser will want to parse it as a variable expression (since it starts with a $), but when it comes across the ., it's going to produce an error.
As it turns out, the SUBQUERY is unnecessary. That same query can be effectively done as:
NSPredicate *p = [NSPredicate predicateWithFormat:#"ANY ThemesList.Themes.Name LIKE %#", #"a theme name"];
Hopefully Core Data will have an easier time handling that than the SUBQUERY.

Core Data: Subquery Performance Problem

I've been trying to see if there is any way I can improve on the performance of the following Predicate:
[NSPredicate predicateWithFormat:#"A=%# and SUBQUERY($self.words,$k,$k.keyword IN %#).#count==%d",
<value for A>,
keywordList,
[keywordList count]];
What I'm trying to do is return all the records of A that have keywords that are ALL contained in the supplied array (keywordList). I have a small database of about 2000 records. However, the keywords for each Entity ranges from 40-300 keywords. The keywords are represented as a to-Many relationship from A to an entity called Keywords. The above query works but takes about 7 seconds to run on my iPhone4. I want to see what I can do to shrink that down to a sub-second response.
Thanks,
Michael
This doesn't quite solve the problem as the results that come back from intersection of the fetchedObjs is not correct. It doesn't guarantee that all the 'A' objects contain all the supplied keywords. In fact, what comes back is an empty list and the entire process actually takes longer.
Something must be wrong with my model or the previous answer. I'm not getting the results I'm expecting. One alternative to returning a list of 'A' would be to return a list of ManagedObjectIDs of 'A'. [mo valueForKey: (NSString *)] returns the object of the relationship.
Note: I originally posted this question anonymously thinking I was logged in, so i can't seem to comment on anybody's answer.
I think you are approaching the problem backwards. If you have the keywords, you should search for the Keyword objects and then walk their A relationships to find the related A objects. Then check for overlap in the relationship sets.
So something like:
NSFetchRequest *fetch=//...set up fetch
[fetch setEntity:Keyword_entity];
NSPredicate *p=[NSPredicate predicateWithFormat:#"keyword IN %#",keywords];
[fetch setPredicate:p];
NSArray *fetchedObj=//... execute fetch
NSMutableSet *inCommon=[NSMutableSet setWithCapacity:[fetchedObjs count]];
for (NSManagedObject *mo in fetchedObjs){
if ([inCommon count]==0){
[inCommon addObjects:[mo valueForKey:#"aRelationshipName"]];
}else{
[inCommon intersectSet:[mo valueForKey:#"aRelationshipName"]];
}
}
... inCommon would then contain a set of all unique A objects that shared all the keywords.
Update:
From OP author:
This doesn't quite solve the problem
as the results that come back from
intersection of the fetchedObjs is not
correct. It doesn't guarantee that all
the 'A' objects contain all the
supplied keywords.
Okay, lets take another tack. Assuming you have a data model that looks something like this:
A {
keywords<<-->>Keyword.as
}
Keyword{
keyword:string
as<<-->>A.keywords
}
Then something like this should work:
NSFetchRequest *keyWordFetch=//...set up fetch
[keyWordFetch setEntity:Keyword_entity];
NSPredicate *p=[NSPredicate predicateWithFormat:#"keyword IN %#",keywords];
[keyWordFetch setPredicate:p];
NSArray *fetchedKeywords=//... execute keyWordFetch
NSFetchRequest *entityAFetch=//...set up fetch
[entityAFetch setEntity:A_entity];
NSPredicate *pred=[NSPredicate predicateWithFormat:#"ALL keywords IN %#", fetchedKeywords);
[entityAFetch setPredicate:pred];
NSArray *fetchedAs=//... execute fetch

Regex for an email address doesn't work

I'm trying to check if some email address is correct with the following code :
NSPredicate *regexMail = [NSPredicate predicateWithFormat:#"SELF MATCHES '.*#.*\..*'"];
if([regexMail evaluateWithObject:someMail])
...
But the "\." doesn't seem to work since the mail "smith#company" is accepted. Besides, I have the following warning : "Unknown escape sequence"
Edit :
I'm programming in Objective-C for iPhone.
Thanks
This is the regular expression used by the iOS Mail application to validate an email address:
^[[:alnum:]!#$%&’*+/=?^_`{|}~-]+((\.?)[[:alnum:]!#$%&’*+/=?^_`{|}~-]+)*#[[:alnum:]-]+(\.[[:alnum:]-]+)*(\.[[:alpha:]]+)+$
And here is a copy/paste ready function using this regular expression to validate an email address in Objective-C:
BOOL IsValidEmail(NSString *email)
{
// Regexp from -[NSString(NSEmailAddressString) mf_isLegalEmailAddress] in /System/Library/PrivateFrameworks/MIME.framework
NSString *emailRegex = #"^[[:alnum:]!#$%&'*+/=?^_`{|}~-]+((\\.?)[[:alnum:]!#$%&'*+/=?^_`{|}~-]+)*#[[:alnum:]-]+(\\.[[:alnum:]-]+)*(\\.[[:alpha:]]+)+$";
NSPredicate *emailPredicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailPredicate evaluateWithObject:email];
}
You cannot correctly validate an email address with regular expressions alone. A simple search will show you many articles discussing this. The problem lies with the nature of DNS: there are too many possible domain names (including non-english and Unicode domains), you cannot correctly validate them using a regex. Don't try.
The only correct way to determine if an email address is valid is to send a message to the address with a unique URL that identifies the account associated with the email, for the user to click. Anything else will annoy your end-user.
Here's a working example, with a slightly more appropriate pattern (although it's not perfect, as others have mentioned):
NSString* pattern = #"[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", pattern];
if ([predicate evaluateWithObject:#"johndoe#example.com"] == YES) {
// Match
} else {
// No Match
}
I guess it should be \\., since \ itself should be escaped as well.
This page has a good explanation of using regular expressions to validate email, as well as some regexes:
http://www.regular-expressions.info/email.html
Their expression:
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
Seems to be the best tradeoff between thoroughness and correctness.
try putting double slash instead of one slash. that's what might the Unknown escape sequence mean.
here is a website that can help you understand how to use regex: http://www.wellho.net/regex/java.html
or just find the appropriate regex for email address here:
http://regexlib.com/DisplayPatterns.aspx?cattabindex=0&categoryId=1
I think youre looking for this. Its a quite comprehensive listing of different regexps and a list of mail addresses for each, stating if the regexp was successful or not.
Copy paste solution (I added capital letters for the first example):
We get a more practical implementation of RFC 5322 if we omit the
syntax using double quotes and square brackets. It will still match
99.99% of all email addresses in actual use today. Source
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
NSString * derivedStandard = #"[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", derivedStandard];
BOOL isValid = [predicate evaluateWithObject:emailAddress];
//doesn't fail on ;)
#"asdkfjaskdjfakljsdfasdkfjaskdjfakljsdfasdkfjaskdjfakljsdfasdkfjaskdjfakljsdf"
For those who want to implement full RFC5322
The official standard is known as RFC 5322. It describes the syntax
that valid email addresses must adhere to. You can (but you
shouldn't--read on) implement it with this regular expression:
(?:[a-z0-9!#$%\&'*+/=?\^_`{|}~-]+(?:\.[a-z0-9!#$%\&'*+/=?\^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
NSString *rfc5322 = #"(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", rfc5322];
BOOL isValid = [predicate evaluateWithObject:emailAddress];
Above regexps you can test using online regular expression tester