Reputation: 4822
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.
Upvotes: 10
Views: 3492
Reputation: 1492
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)
Upvotes: 0
Reputation: 655
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
Upvotes: 0
Reputation: 539735
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:@"*"];
}];
Upvotes: 21