2
votes

I have data stored in this fashion,

var data = [(String, Array<String>)]()

Example Data:

[(A, [Apple, Andy, Android]), (B, [Banana, Breakfast])]

I am trying to apply a search filter on this data by using Predicate,

Here is what I am tried and failed,

func updateSearchResultsForSearchController(searchController: UISearchController) {
      let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %@", searchController.searchBar.text) 
      let array = (data as NSArray).filteredArrayUsingPredicate(searchPredicate)
      data = array as! [(String, Array<String>)]

      //reload tableView with fresh data
      self.tableView.reloadData()
}

Its failing as I am trying to convert my data into NSArray to apply filter and then trying to convert the filtered array back to my desired format. (I know that this conversion won't work, this is just to give you guys an idea of what I am trying to do.)

My question is how to apply search filter on my data's array of Strings ?

As in, if the search term is "an", the data should filter to

[(A, [Andy, Android]), (B, [Banana])]
2
Whoever just answered my question and then deleted it, please post it again! I just tried it and it works but the search is happening on the first param of the data but not on the second param, which is my String array. I think we are close, can you explain me the code please? - Nagendra Rao
I was just editing my answer, just posted it now. It doesn't look like an elegant solution, but it works - Midhun MP

2 Answers

3
votes

Since your array contains tuples, instead of using NSPredicate, you can use the following method for filtering your data:

func getFilteredData(data : [(String, Array<String>)], ltrToCompare : String) -> [(String, Array<String>)]
{
    // For keeping the filtered result
    var returnData = [(String, Array<String>)]()

    // Looping through parent array        
    for (letter, arr) in data
    {
        // Filters the internal array [String]
        let filter = arr.filter()
        {
            return $0.lowercaseString.rangeOfString(ltrToCompare.lowercaseString) != nil
        }

        // Checks whether the inner array filtering returns any element
        if (filter.count != 0)
        {
            returnData.append((letter, filter));
        }
    }
    return returnData
}

Call that method from:

func updateSearchResultsForSearchController(searchController: UISearchController)
{
      data = getFilteredData(data, ltrToCompare: searchController.searchBar.text)
      //reload tableView with fresh data
      self.tableView.reloadData()
}
2
votes

A possible solution with map() and filter() (Swift 1.2):

let data = [("A", ["Apple", "Andy", "Android"]), ("B", ["Banana", "Breakfast"])]
let searchText = "an"

let filtered = map(data) {
    ($0, $1.filter {
        $0.rangeOfString(searchText, options: .CaseInsensitiveSearch) != nil
    })
}.filter { !isEmpty($1) }

println(filtered)
// [(A, [Andy, Android]), (B, [Banana])]

The first map() maps each (letter, words) tuple to (letter, filteredWords) according to the search text. The following filter() then removes the tuples for which filteredWords is empty.

Only minor changes are necessary for Swift 2:

let filtered = data.map {
    ($0, $1.filter {
        $0.rangeOfString(searchText, options: .CaseInsensitiveSearch) != nil
    })
}.filter { !$1.isEmpty }