Search code examples
iosswiftuitableviewuisearchbaruisearchcontroller

UISearchController returns section instead of the filtered tableview item


I have implemented a SearchController and when I search the food item using the SearchController, it returns the whole section.

For example, I will search Chocolate using the SearchController and it would return and result in the tableView displaying the whole section of Sweets, instead of just Chocolate, it'll return Lollipops as well. What am I doing wrongly here? I am fairly new to swift, any help would be appreciated. Thank you.

struct Food {
    let foodTaste: String
    let foodList: [String]
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {

    func updateSearchResults(for searchController: UISearchController) {
        if searchController.searchBar.text! == "" {
            filteredFood = food
        } else {
            filteredFood = food.filter { $0.foodList.contains(where: { $0.contains(searchController.searchBar.text!) }) }
            self.tableView.reloadData()  
        }  
    }

    var tableView = UITableView()
    var food = [Food]()
    var filteredFood = [Food]()

    let searchController = UISearchController(searchResultsController: nil)

    override func viewDidLoad() {
        super.viewDidLoad()

        filteredFood = food
        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        tableView.tableHeaderView = searchController.searchBar
        definesPresentationContext = true
        tableView.frame = self.view.frame
        self.view.addSubview(tableView)

        tableView.delegate = self
        tableView.dataSource = self

        let sweets = Food(foodTaste: "Sweet", foodList: ["Chocolate", 
        "Lollipops"])
        let sour = Food(foodTaste: "Sour", foodList: ["Lemon", "Limes"])
        food.append(sweets)
        food.append(sour)
    }


    func numberOfSections(in tableView: UITableView) -> Int {
        return filteredFood.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return filteredFood[section].foodList.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        cell.textLabel?.text = filteredFood[indexPath.section].foodList[indexPath.row]
        return cell
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return filteredFood[section].foodTaste
    }

    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 4
    }


}

Solution

  • The bus is in the line

            filteredFood = food.filter { $0.foodList.contains(where: { $0.contains(searchController.searchBar.text!) }) }
    

    Effectively your food and filteredFood are a 2-dimensional array. The first dimension is the array itself. The second - inner array foodList inside the Food object. This code filter only the outer dimension of this data structure. It says that if there is a string that matches search inside the foodList, then whole Food object should pass the filter. If this is not what you want - you have to create new Food objects as well. Probably something like this:

            filteredFood = food.filter { $0.foodList.contains(where: { $0.contains(searchController.searchBar.text!) }) }
                               .map  { Food(foodTaste: $0.foodTaste, foodList: $0.foodList.filter( {$0.contains(searchController.searchBar.text!) }) }