Search code examples
iosswiftnspredicate

Swift 4: NSPredicate from UITextField and array of custom object


I have an array of custom object.

class MyObject {
    var code: String!
    var name: String!
}

I want to autocomplete a textField for that I have a tableView that I display when the user start writing. I have to filter my array datasource depending on the textField.text. For that I added a selector to the textField, then I test if there are names of elements in the array containing the textFiled.text using NSPredicate.

This is the textField selector:

    @objc func textFieldDidChange(_ textField: UITextField) {
            if (textField.text?.count != 0 && textField.text != " ") {
                let resultPredicate = NSPredicate(format: "ANY name CONTAINS[c] %@", self.countryTextField.text!)
                print("resultPredicate \(resultPredicate)")
//self.allDatasource type is [MyObject]
                self.filtredDatasource = self.allDatasource.filter({
                    return resultPredicate.evaluate(with: $0.name)
                })
            } else {
                self.filtredDatasource = self.allDatasource
            }
            self.tableView.reloadData()
        }

When I start writing a name the application crash due to this error:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x6040004215e0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'

So the problem is in NSPredicate format. I searched about this issu and I found that I can use ANY then put the field name that I want to check.

Am I wrong? How can I solve this problem?

Note: I'm using Swift 4


Solution

  • I think for this to properly work, you need to make your object available to the Objective-C side:

    class MyObject: NSObject {
        init(code: String, name: String) {
            self.code = code
            self.name = name
        }
    
        var code: String
        @objc var name: String
    }
    

    The predicate is evaluated over the object you give it. In your case, you provide it a String. For the predicate to check that, you should use self:

    let resultPredicate = NSPredicate(format: "self contains[cd] %@", searchText)
    
    let filtered = allDatasource.filter {
        resultPredicate.evaluate(with: $0.name)
    }
    

    You can also give it a MyObject to evaluate, then you can use the name of the property:

    let resultPredicate = NSPredicate(format: "name contains[cd] %@", searchText)
    
    let filtered = allDatasource.filter {
        resultPredicate.evaluate(with: $0)
    }
    

    In your code, you use the ANY modifier. That's a modifier you can use to check if there is any item in a collection that adheres to the predicate:

    let searchText = "n2"
    let resultPredicate = NSPredicate(format: "ANY name contains[cd] %@", searchText)
    
    let allDatasource = [ MyObject(code: "c1", name: "n1"), MyObject(code: "c2", name: "n2")]
    let containsAnyObject = resultPredicate.evaluate(with: allDatasource)
    
    // true for 'n2'