Searching Contacts using NSExpressions and NSPredicates

267 Views Asked by At

I've been able to filter a table of contacts by the contact's first name, last name, company, and notes. However, I also need to be able to search through the contact's postal addresses for the search terms. Has anyone worked with this before and has any idea how to do so using the NSExpression/NSPredicate methodology and not simply looping through the contacts?

I have over 6000 contacts to go through so looping isn't very efficient. I've started to try to figure it out but am failing miserably. I keep getting the following error: Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key city.', but I know I've provided the descriptor when fetching the contacts. Again, any help is appreciated and I've pasted my code below.

func updateSearchResults(for searchController: UISearchController) {
    // Update the filtered array based on the search text.
    let searchResults = contacts

    // Strip out all the leading and trailing spaces.
    let whitespaceCharacterSet = CharacterSet.whitespaces
    let strippedString = searchController.searchBar.text!.trimmingCharacters(in: whitespaceCharacterSet)
    let searchItems = strippedString.components(separatedBy: " ") as [String]

    // Build all the "AND" expressions for each value in the searchString.
    let andMatchPredicates: [NSPredicate] = searchItems.map { searchString in
        var searchItemsPredicate = [NSPredicate]()

        // Key Path field matching.
        let firstNameExpression = NSExpression(forKeyPath: CNContactGivenNameKey)
        let lastNameExpression = NSExpression(forKeyPath: CNContactFamilyNameKey)
        let companyNameExpression = NSExpression(forKeyPath: CNContactOrganizationNameKey)
        let noteExpression = NSExpression(forKeyPath: CNContactNoteKey)
        //let addressesExpressions = NSExpression(forKeyPath: CNPostalAddressCityKey)

        var searchStringExpression: NSExpression!
        if searchString == "$" || searchString == "$$" {
            searchStringExpression = NSExpression(forConstantValue: "customer")
        } else {
            searchStringExpression = NSExpression(forConstantValue: searchString)
        }

        let firstNameSearchComparisonPredicate = NSComparisonPredicate(leftExpression: firstNameExpression, rightExpression: searchStringExpression, modifier: .direct, type: .contains, options: .caseInsensitive)
        let lastNameSearchComparisonPredicate = NSComparisonPredicate(leftExpression: lastNameExpression, rightExpression: searchStringExpression, modifier: .direct, type: .contains, options: .caseInsensitive)
        let companyNameSearchComparisonPredicate = NSComparisonPredicate(leftExpression: companyNameExpression, rightExpression: searchStringExpression, modifier: .direct, type: .contains, options: .caseInsensitive)
        let noteSearchComparisonPredicate = NSComparisonPredicate(leftExpression: noteExpression, rightExpression: searchStringExpression, modifier: .direct, type: .contains, options: .caseInsensitive)
        //let addressesSearchComparisonPredicate = NSComparisonPredicate(leftExpression: addressesExpressions, rightExpression: searchStringExpression, modifier: .direct, type: .contains, options: .caseInsensitive)

        searchItemsPredicate.append(firstNameSearchComparisonPredicate)
        searchItemsPredicate.append(lastNameSearchComparisonPredicate)
        searchItemsPredicate.append(companyNameSearchComparisonPredicate)
        searchItemsPredicate.append(noteSearchComparisonPredicate)
        //searchItemsPredicate.append(addressesSearchComparisonPredicate)

        // Add this OR predicate to our master AND predicate.
        let orMatchPredicate = NSCompoundPredicate(orPredicateWithSubpredicates:searchItemsPredicate)

        return orMatchPredicate
    }

    // Match up the fields of the Product object.
    let finalCompoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: andMatchPredicates)
    let filteredResults = searchResults.filter { finalCompoundPredicate.evaluate(with: $0) }

    // Hand over the filtered results to our search results table.
    self.filteredContacts = filteredResults
    self.tableView.reloadData()
}
0

There are 0 best solutions below