squarefrog
squarefrog

Reputation: 4822

NSPredicate match any characters

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.

Open Quickly Menu

Upvotes: 10

Views: 3492

Answers (3)

wlixcc
wlixcc

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

FrugalResolution
FrugalResolution

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

Martin R
Martin R

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

Related Questions