Skip to main content
Notice removed Authoritative reference needed by Chris
Bounty Ended with Vitalii Gozhenko's answer chosen by Chris
Tweeted twitter.com/StackCodeReview/status/1221583728910139394
Notice added Authoritative reference needed by Chris
Bounty Started worth 50 reputation by Chris
edited tags
Link
Chris
  • 226
  • 3
  • 18

Searching within a data model using an array matching multiple terms

Source Link
Chris
  • 226
  • 3
  • 18

Searching within an array matching multiple terms

I have a standard tableview app that displays information from a data model array that uses a custom class. There is a UISearchController to filter the data.

A simplified version of the custom class is shown. I use a computed property for a string that is checked when searching the data.

class myObject: NSObject {
    
    // Inherits from NSObject as I use NSCoding for persistence
    
    var id: String = UUID().uuidString // These are unique for each item
    
    var name: String = ""
    var type: String = ""
    var batch: String = ""

    var searchableString: String {
        // This is a concatenation of some of the properties
        return self.name + " " + self.type + " " + self.batch
    }

    // etc...
}

Every time the user types a new character into the search bar of the UISearchController, the following method is called, which filters the main data array into a filteredItems array. Initially, I used a very basic form where the entire search text had to match.

func searchItems() {
    if let searchText = searchController.searchBar.text {
        filteredItems = allItems.filter { item in
            return item.searchableString.lowercased().contains(searchText.lowercased())
        }
    }
}

filteredItems is then displayed in the tableview.

I wanted to improve this so that each word typed in the search bar is searched for separately. I tried this, but of course it filters using OR logic (any word can match).

func searchItems() {
    if let searchText = searchController.searchBar.text?.split(separator: " ") {
        // Need to convert substring from split back to string
        let searchArray = searchText.map() { substring in
            String(substring)
        }
        filteredItems = allItems.filter { item in
            return searchArray.contains(where: drug.searchableString.lowercased().contains)
        }
    }
}

In order to get the desired AND logic (every word typed must match), I had to use this.

func searchItems() {
    if let searchText = searchController.searchBar.text?.split(separator: " ") {
        // Need to convert substring from split back to string
        let searchArray = searchText.map() { substring in
            String(substring)
        }
        filteredItems = allItems.filter { item in
            for word in searchArray {
                if !item.searchableString.lowercased().contains(word) {
                    return false
                }
            }
            return true
        }
    }
}

This works as intended, but it does not seem elegant. Is there a better way to filter the array allItems to filteredItems based on matching text in those String properties of the objects in my data model?