NSPredicate match any characters - iphone

How do I construct an NSPredicate that looks for the search terms anywhere in an Arrays objects? I can't quite explain it properly, so here's an example.
NSArray *array = #[#"Test String: Apple", #"Test String: Pineapple", #"Test String: Banana"];
NSString *searchText = #"Apple";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF CONTAINS[cd] %#", searchText];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];
NSLog(#"filteredArray: %#", filteredArray);
// filteredArray: (
// "Test String: Apple",
// "Test String: Pineapple"
// )
But if I use NSString *searchText = #"Test Str Appl"; I get zero results. I'd like it to match the same results for this string.
What I'm looking for is a search function that is similar to the "Open Quickly" menu in Xcode, where it doesn't matter if your search string is worded correctly, only that the letters are in the correct order as a match. I really hope that makes sense.

The LIKE string comparison in predicates allows for wildcards * and ?, where * matches 0 or more characters. Therefore, if you transform your search text into
NSString *searchWithWildcards = #"*T*e*s*t* *S*t*r*A*p*p*l*";
by inserting a * at the beginning, between all characters, and at the end of the original search text by using something like this
NSMutableString *searchWithWildcards = [NSMutableString stringWithFormat:#"*%#*", self.searchField.text];
if (searchWithWildcards.length > 3)
for (int i = 2; i < self.searchField.text.length * 2; i += 2)
[searchWithWildcards insertString:#"*" atIndex:i];
then you can use
[NSPredicate predicateWithFormat:#"SELF LIKE[cd] %#", searchWithWildcards];
The predicate searches for the characters in the given order, with arbitrary other characters between them.
The transformation can for example be done with the following code:
NSMutableString *searchWithWildcards = [#"*" mutableCopy];
[searchText enumerateSubstringsInRange:NSMakeRange(0, [searchText length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[searchWithWildcards appendString:substring];
[searchWithWildcards appendString:#"*"];
}];

Because I can't read objcC that good, I wrote my own wildcard function. Here we go:
Swift 5
CoreData is fetched into initialized array
var originalArray: [NSManagedObject] = []
extension myViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filteredArray.removeAll(keepingCapacity: true)
if searchController.searchBar.text!.isEmpty {
filteredArray = originalArray
} else {
let filter = searchController.searchBar.text!
var filterWithWildcards = ""
for i in 0 ..< filter.length {
if i == 0 {
filterWithFormat = "*"
}
filterWithFormat += filter[i] + "*"
}
print("filter: \(filter); filterWithWildcards: \(filterWithWildcards)") // filter: Mxmstrmnn; filterWithFormat: *M*x*m*s*t*r*m*n*n*
myPredicate = NSPredicate(format: "EnterYourAttributeNameOfCoreDataEntity LIKE[c] %#", filterWithWildcards)
let array = (people as NSArray).filtered(using: myPredicate!)
filteredArray = array as! [NSManagedObject]
}
myTableView.reloadData()
}}
The result in filteredArray finds Max Mustermann as long as I keep the order of letters.
To get the index letter of filter[i], I use a String extension from here https://stackoverflow.com/a/26775912/12035498

Thanks to Martin R
There is my swift5 version
let wildcardSting = "*" + text.map { "\($0)*" }.joined()
let predicate = NSPredicate.init(format: "%K LIKE[cd] %#", #keyPath(Record.name), wildcardSting)

Related

Objective regex evaluateWithObject is not working

When I tried matching the string with the regex '^(34|37)' it does not work even after giving the correct one. Can anyone please point out or guide me to what I am doing wrong?
This is my code:
NSPredicate *myTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", #"^(34|37)"];
if([myTest evaluateWithObject: #"378282246310005"]){
NSLog(#"match");
}
Your regex will not match the given string. That is ^(34|37) does not match 378282246310005. It matches the first two characters, but after that it fails because the string contains more characters, while your regex terminates.
You need to alter your regex to match the rest of the characters, even if you don't want to capture them. Try changing your regext to ^(34|37).*.
Make seprate method for matching regex as bool type. Then it will work.
like this
- (IBAction)tapValidatePhone:(id)sender
{
if(![self validateMobileNo:self.txtPhoneNo.text] )
{
NSLog(#"Mobile No. is not valid");
}
}
-(BOOL) validateMobileNo:(NSString *) paramMobleNo
{
NSString *phoneNoRegex = #"^(34|37)";
NSPredicate *phoneNoTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#",phoneNoRegex];
return [phoneNoTest evaluateWithObject:#"3435"];
}
it is not going in else condition.
Why not just use hasPrefix:
if([#"378282246310005" hasPrefix:#"34"] || [#"378282246310005" hasPrefix:#"37"])
{
NSLog(#"found it");
}
EDIT:
Using NSPredicate:
NSPredicate *myTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", #"^3(4|7)\\d+$"];
if([myTest evaluateWithObject: #"378282246310005"])
{
NSLog(#"match");
}
else
{
NSLog(#"notmatch");
}
Using NSRegularExpression:
NSError *error = nil;
NSString *testStr = #"348282246310005";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"^3(4|7)" options:NSRegularExpressionCaseInsensitive error:&error];
NSInteger matches = [regex numberOfMatchesInString:testStr options:NSMatchingReportCompletion range:NSMakeRange(0, [testStr length])];
if(matches > 0 )//[myTest evaluateWithObject: #"378282246310005"])
{
NSLog(#"match");
}
else
{
NSLog(#"notmatch");
}
BTW: (34|37) does not look 34 or 37 instead it seems 347 or 337 to me, since engine will pick 4|3 either 4 or 3.

Searching for a keyword in a string, in an NSArray

I've got an array of keywords and an array of strings.
I'm currently iterating out the keywords and using a filter on the strings array to determine whether or not the keyword is in there (in some form).
The below code works, however when there is a keyword (or the same characters as the keyword) within another word, this is flagged. ie. Searching for bon in string ribbon would flag ribbon. I don't want to do an exact comparison as it's possible the keyword will be surrounded by other characters / words in the string.
Is there a way I can search for it and only flag it if it's surrounded by whitespace or brackets? ie. Not part of another word..
NSArray *paInc = [productIncludes valueForKey:pa];
// This is the array of keywords
NSMutableArray *paMatchedIncludes = [[NSMutableArray alloc] init];
for (id include in paInc){
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains [cd] %#", include];
NSArray *filteredArray = [stringArray filteredArrayUsingPredicate:predicate];
// stringArray is the array containing the strings I want to search for these keywords
for (NSString *ing in filteredArray){
if ([ing length] > 0){
if (![paMatchedIncludes containsObject:[NSString stringWithFormat:#"%#",ing]]){
[paMatchedIncludes addObject:[NSString stringWithFormat:#"%#",ing]];
}
}
}
}
Does the following code solve your problem?
NSArray *paInc = #[#"bon",
#"ssib"];
// This is the array of keywords
NSArray *stringArray = #[#"Searching for bon in string ribbon would flag ribbon.",
#"I don't want to do an exact comparison as it's possible the keyword will be surrounded by other characters / words in the string."];
// stringArray is the array containing the strings I want to search for these keywords
NSMutableArray *paMatchedIncludes = [[NSMutableArray alloc] init];
for (id include in paInc){ // for every keyword
for (NSString *nextString in stringArray) { // for every string
NSArray *components = [nextString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#" ()"]];
if ([components containsObject:include]) {
[paMatchedIncludes addObject:nextString];
}
}
}
EDIT (due to your comment): For case insensitive compares:
for (id include in paInc){ // for every keyword
for (NSString *nextString in stringArray) { // for every string
NSArray *components = [nextString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#" ()"]];
for (NSString *nextComponent in components) {
if([nextComponent caseInsensitiveCompare:include] == NSOrderedSame)
[paMatchedIncludes addObject:nextString];
}
}
}
I guess regular expression is what you want.

How to implement searching of a string in an array of dictionaries?

I have an application in which i have an nsmutable array created like this .`
NSDictionary *memberInfo = [self.currentChannel infoForMemberWithID:memberID];
for(int i=0;i<self.currentChannel.memberCount;i++)
{
[searchfriendarray addObject:memberInfo];
}
NSLog(#"dfdfdfsear%#",searchfriendarray);
The response i am getting is this
dfdfdfsear(
{
name = baddd;
status = "<null>";
},
{
name = baddd;
status = "<null>";
}
)
`
Now i want to search an nsstring in this.with the method
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
Can anybody help me in implementing the search and load my tableview according to that?
Use NSPredicate to filter the array.
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:#"name contains[cd] %#", searchString];
[yourMutableArray filterUsingPredicate:filterPredicate];
You can use NSPredicate for this.
Predicate
A predicate is a useful object that filters an array. It’s a bit like having a select with a simple where clause in SQL.
If you have array in this format
NSMutableArray *yourAry;
(
{
category = classic;
quote = "Frankly, my dear, I don't give a damn.";
source = "Gone With the Wind";
},
{
category = classic;
quote = "Here's looking at you, kid.";
source = Casablanca;
}
{
category = modern;
quote = "I solemnly swear that I am up to no good.";
source = "Harry Potter and the Prisoner of Azkaban";
},
{
category = modern;
quote = "You like pain? Try wearing a corset.";
source = "Pirates of the Caribbean";
}
)
And you want to search all the array which category = classic then we can use NSPredicate.
NSString *selectedCategory = #"classic";
//filter array by category using predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"category == %#", selectedCategory];
NSArray *filteredArray = [self.movieQuotes filteredArrayUsingPredicate:predicate];
Follow this blog if you need any help MyBlog

How retrieve an index of an NSArray using a NSPredicate?

I would know how retrieve an index of an NSArray using a NSPredicate ?
NSArray *array = [NSArray arrayWithObjects:
#"New-York City",
#"Washington DC",
#"Los Angeles",
#"Detroit",
nil];
Which kind of method should I use in order to get the index of "Los Angles" by giving only a NSString?
NB: #"Los An" or #"geles" should return the same index.
Using NSPredicate you can get array of strings that contain your search string (it seems there's no built-in method to get just element indexes):
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF CONTAINS[cd] %#", searchString];
NSArray *filteredArray = [array filteredArrayUsingPredicate: predicate];
You can get only indexes using indexesOfObjectsPassingTest: method:
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop){
NSString *s = (NSString*)obj;
NSRange range = [s rangeOfString: searchString];
return range.location != NSNotFound;
}];
If you want to get just one element containing your string you can use similar indexOfObjectPassingTest: method for that.
You should be able to do this with blocks. Below is a snippet (I don't have a compiler handy so pls excuse any typos):
NSArray *array = [NSArray arrayWithObjects:
#"New-York City",
#"Washington DC",
#"Los Angeles",
#"Detroit",
nil];
NSString *matchCity = #"Los";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %#", matchCity];
NSUInteger index = [self.array indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
return [predicate evaluateWithObject:obj];
}];
Essentially you can use the indexOfObjectPassingTest: method. This takes a block (code following the "^") and returns the index for the first object that matches your predicate (or NSNotFound if no match exists). The block iterates through each object in the array until either a match is found (at which point it returns the index) or no match is found (at which point it returns NSNotFound). Here is a link to block programming that can help you understand the logic within the block:
https://developer.apple.com/library/ios/featuredarticles/Short_Practical_Guide_Blocks/
Found an alternative approach helpful where the search is more complex as it allows predicate to be used to find object then object to find index:
-(NSIndexPath*) indexPathForSelectedCountry{
NSUInteger indexToCountry = 0;
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"isoCode = %#",self.selectedCountry.isoCode];
NSArray * selectedObject = [self.countryList filteredArrayUsingPredicate:predicate];
if (selectedObject){
if (self.searchDisplayController.isActive){
indexToCountry = [self.searchResults indexOfObject:selectedObject[0]];
}else{
indexToCountry = [self.countryList indexOfObject:selectedObject[0]];
}
}
return [NSIndexPath indexPathForRow:indexToCountry inSection:0];
}
I would do this..
NSString * stringToCompare = #"geles";
int foundInIndex;
for ( int i=0; i<[array count]; i++ ){
NSString * tryString = [[array objectAtIndex:i] description];
if ([tryString rangeOfString:stringToCompare].location == NSNotFound) {
// no match
} else {
//match found
foundInIndex = i;
}
}// end for loop
Based on #Louie answer, instead of using for loop i had used enumeration block which worked for me.
I did this :-
NSString *stringToCompare = #"xyz";
[myArray enumerateObjectsUsingBlock:^(id *Obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString * tryString = [[myArray objectAtIndex:idx] description];
if ([tryString rangeOfString:stringToCompare].location == NSNotFound) {
// no match found
} else {
//match found and perform your operation. In my case i had removed array object at idx
}
}];

How do I create the right NSPredicate for this kind of request?

Hi the problem goes like this:
I have in CoreData entities that have a title and a relationship to keywords entities.
I need a predicate that helps me to fetch all those entities whose title contains the keywords I type. I have the code below that should do this but it doesn't:
NSArray *keywords = [searchString componentsSeparatedByString:#" "];
NSString *predicateString = #"";
for(NSInteger i = 0; i < [keywords count]; i++) {
if(((NSString*)[keywords objectAtIndex:i]).length != 0) {
if(i==0) {
predicateString = [keywords objectAtIndex:i];
}
else {
predicateString = [predicateString stringByAppendingFormat:#" and keywords.normalizedKeyword contains[cd] %#", [keywords objectAtIndex:i]];
}
}
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"keywords.normalizedKeyword contains[cd] %#",predicateString];
For example I have entities with titles like this:
1) "Great Coloring theme"
2) "Theme for kids"
3) "Cars for kids"
my keywords db will contain:
great
coloring
theme
for
kids
cars
How can I create a predicate so when I type for example:
Theme for
the result will be 2) and 3)
or if I type:
great theme
the result will be 1) and 2)
Any help in getting the right predicate to achieve this is very much appreciated. What I tried to do there it doesn't work and I am out of ideas.
Thanks!
I have found the answer myself. To solve such a problem you have to use a NSCompoundPredicate.
The solution to my problem was this:
NSArray *keywords = [lowerBound componentsSeparatedByString:#" "];
NSMutableArray *predicates = nil;
for(NSInteger i = 0; i < [keywords count]; i++) {
if(((NSString*)[keywords objectAtIndex:i]).length != 0) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"keywords.normalizedKeyword contains[cd] %#", [keywords objectAtIndex:i]];
if(predicates == nil) {
predicates = [[NSMutableArray alloc] initWithCapacity:0];
}
[predicates addObject:predicate];
}
}
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];