-2

I have a search bar above my table view. The data in table view is coming from my service. I'm trying to apply search filter on the table view data. I have tried some code but it isn't working. My code for searchbar is this,

    UIViewController,UISearchBarDelegate,UITextFieldDelegate,UITextViewDelegate,ShowsAlert
    @IBOutlet weak var searchBar: UISearchBar!

    var filteredData = [String]()
    var isSearching = false
    var dishNameArray = [String]()

     override func viewDidLoad() {
        super.viewDidLoad()

        menuView.isHidden = true
        reviewView.isHidden = true
        infoView.isHidden = true
        scrollView.isScrollEnabled = false

        //TableView Delegates
        menuTableView.delegate = self
        menuTableView.dataSource = self
        reviewTableView.delegate = self
        reviewTableView.dataSource = self
        reviewTableView.reloadData()
        searchBar.delegate = self
        searchBar.returnKeyType = UIReturnKeyType.done

        segmentControl.tintColor = #colorLiteral(red: 0.9529120326, green: 0.3879342079, blue: 0.09117665142, alpha: 1)

        searchBar.delegate = self

        dishNameLbl.text = name
        dishDescripLbl.text = resDesc
        minOrderLbl.text = minOrder
        deliveryLbl.text = deliveryTime

    }
     private func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
        isSearching = true
    }

    private func searchBarTextDidEndEditing(searchBar: UISearchBar) {
        isSearching = false
    }

    private func searchBarCancelButtonClicked(searchBar: UISearchBar) {
        isSearching = false
    }

    private func searchBarSearchButtonClicked(searchBar: UISearchBar) {
        isSearching = false
    }

    private func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {

        guard let searchText = searchBar.text else {
            isSearching = false
            return
        }

        filteredData = dishNameArray.filter({
            return $0.lowercased().contains(searchText.lowercased())
        })

        isSearching = filteredData.count > 0
        self.menuTableView.reloadData()
    }

    extension RestaurantMenuVC: UITableViewDelegate,UITableViewDataSource{


    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == menuTableView{
            if isSearching{
                return filteredData.count
            }
        return ResMenuService.instance.categoryModelInstance.count
        }
        else{
        return AllReviewsService.instance.allReviewsModel.count
        }
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if tableView == menuTableView{
            return 57
        }
        else{
            return 137
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if tableView == menuTableView{
            let cell = menuTableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! RestaurantMenuTableViewCell

        if isSearching{

            cell.dishTitleLbl.text = filteredData[indexPath.row]
            dishNameArray.append(cell.dishTitleLbl.text!)

            }
//        let cell = menuTableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! RestaurantMenuTableViewCell

        cell.dishTitleLbl.text = ResMenuService.instance.categoryModelInstance[indexPath.row].categoryName
        cell.cardView.layer.cornerRadius = 5
        cell.selectionStyle = .none
        return cell

        }
        else
        {
            let cell = reviewTableView.dequeueReusableCell(withIdentifier: "reviewCell", for: indexPath) as! AllReviewsTableViewCell

            cell.nameLbl.text = AllReviewsService.instance.allReviewsModel[indexPath.row].name
            cell.descriptionLbl.text = AllReviewsService.instance.allReviewsModel[indexPath.row].description
            cell.timeLbl.text = AllReviewsService.instance.allReviewsModel[indexPath.row].time
            cell.ratingView.rating = Double(AllReviewsService.instance.allReviewsModel[indexPath.row].rating)
            cell.backgroundColor = UIColor.clear
            cell.selectionStyle = .none
            return cell

        }
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        if tableView == menuTableView{

           let minimumSpending = String(ResMenuService.instance.restaurntDetailModelInstance[indexPath.row].minimumSpending)

            UserDefaults.standard.set(minimumSpending, forKey: "minimumSpending")
            UserDefaults.standard.synchronize()

            let categoryModel = ResMenuService.instance.categoryModelInstance
            let subCategoryModel = ResMenuService.instance.categoryModelInstance[indexPath.row].subCategories
            let AddonCategoryModel = ResMenuService.instance.categoryModelInstance[indexPath.row].subCategories[0].items[0].addonCategory


//            if categoryId == subCategoryId{


                let storyboard = UIStoryboard(name: "Main", bundle: nil)
                        let vc = storyboard.instantiateViewController(withIdentifier: "RestaurantMenuDetailVC") as! RestaurantMenuDetailVC
                        vc.categoryModel = categoryModel
                        vc.subCategoryModel = subCategoryModel
                        vc.AddonCategoryModel = AddonCategoryModel
                        self.navigationController?.pushViewController(vc, animated: true)

            //}
        }
        else{
            print("Hello")
        }
    }


    }

But when i type something it does not filter the data. here is my model class,

struct RestaurantDetailModel {

    public private(set) var restaurantId:String!
    public private(set) var shopLat:String!
    public private(set) var shopLng:String!
    public private(set) var street:String!
    public private(set) var town:String!
    public private(set) var zipCode:String!
    public private(set) var cellNo:String!
    public private(set) var landLine:Int!
    public private(set) var shopName:String!
    public private(set) var deliveryTime:Int!
    public private(set) var collectionTime:Int!
    public private(set) var facebookLink:String!
    public private(set) var twitterLink:String!
    public private(set) var googleLink:String!
    public private(set) var instagramLink:String!
    public private(set) var pinterestLink:String!
    public private(set) var address:String!
    public private(set) var preorderPref:String!
    public private(set) var orderStatus:Bool!
    public private(set) var minimumSpending:Int!

    public private(set) var restaurantTimings:[RestaurantTimingsModel]!

}
Scriptable
  • 19,402
  • 5
  • 56
  • 72
raheem
  • 689
  • 2
  • 8
  • 16

3 Answers3

2

You dont set isSearching to true anywhere. So it is always false.

So the table never uses filteredData until you set isSearching = true

func updateSearchResults(for searchController: UISearchController) is a UISearchControllerDelegate method. As you are not using a UISearchController this will not be called in your case. You need to be using the UISearchBarDelegate functions.

Try the below changes. Reference used

in viewDidLoad add the following line:

searchBar.delegate = self

Add the following functions:

func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
    isSearching = true
}

func searchBarTextDidEndEditing(searchBar: UISearchBar) {
    isSearching = false
}

func searchBarCancelButtonClicked(searchBar: UISearchBar) {
    isSearching = false
}

func searchBarSearchButtonClicked(searchBar: UISearchBar) {
    isSearching = false
}

Change your updateSearchResults function to this:

func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {

    guard let searchText = searchBar.text else {
       isSearching = false
       return
    }

    filteredData = dishNameArray.filter({
        return $0.lowercased().contains(searchText.lowercased())
    })

    isSearching = filteredData.count > 0
    self.menuTableView.reloadData()
}

You also need to make your ViewController conform to UISearchBarDelegate so add it, something like this:

class ViewController: UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate

This is much easier when using a UISearchController. See the example below.

Example using UISearchController:

I have just put this example together in a playground which shows how to do this with a UISearchController.

import UIKit
import PlaygroundSupport

class ViewController: UITableViewController {

    let searchController = UISearchController(searchResultsController: nil)

    var names = [
        "John",
        "Terry",
        "Martin",
        "Steven",
        "Michael",
        "Thomas",
        "Jason",
        "Matthew"
    ]
    var filteredNames = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Search Example"

        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Search"
        navigationItem.searchController = searchController
        definesPresentationContext = true
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if isFiltering() {
            return filteredNames.count
        } else {
            return names.count
        }
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell() // don't do this, i am for example.

        var name: String
        if isFiltering() {
            name = filteredNames[indexPath.row]
        } else {
            name = names[indexPath.row]
        }

        cell.textLabel?.text = name
        return cell
    }

    func searchBarIsEmpty() -> Bool {
        // Returns true if the text is empty or nil
        return searchController.searchBar.text?.isEmpty ?? true
    }

    func isFiltering() -> Bool {
        return searchController.isActive && !searchBarIsEmpty()
    }

    func filterContentForSearchText(_ searchText: String, scope: String = "All") {
        filteredNames = names.filter({( name : String) -> Bool in
            return name.lowercased().contains(searchText.lowercased())
        })

        tableView.reloadData()
    }
}

extension ViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        filterContentForSearchText(searchController.searchBar.text!)
    }
}

let vc = ViewController()
let nav = UINavigationController()
nav.viewControllers = [vc]

PlaygroundPage.current.liveView = nav

EDIT 2: Working SearchBar

import UIKit
import PlaygroundSupport

class ViewController: UITableViewController, UISearchBarDelegate {

    var searchBar: UISearchBar!
    var isFiltering = false

    var names = [
        "John",
        "Terry",
        "Martin",
        "Steven",
        "Michael",
        "Thomas",
        "Jason",
        "Matthew"
    ]
    var filteredNames = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Search Example"

        searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
        searchBar.delegate = self
        tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
        tableView.tableHeaderView?.addSubview(searchBar)
    }

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        isFiltering = false
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        isFiltering = false
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        isFiltering = true
    }

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        isFiltering = false
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        guard let searchText = searchBar.text else {
            isFiltering = false
            return
        }

        filteredNames = names.filter({
            return $0.lowercased().contains(searchText.lowercased())
        })

        isFiltering = filteredNames.count > 0
        self.tableView.reloadData()
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if isFiltering {
            return filteredNames.count
        } else {
            return names.count
        }
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell() // don't do this, i am for example.

        var name: String
        if isFiltering {
            name = filteredNames[indexPath.row]
        } else {
            name = names[indexPath.row]
        }

        cell.textLabel?.text = name
        return cell
    }

}

let vc = ViewController()
PlaygroundPage.current.liveView = vc

EDIT 3: New information provided

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    guard let searchText = searchBar.text else {
        isFiltering = false
        return
    }

    filteredData = ResMenuService.instance.categoryModelInstance.filter({
        return $0.categoryName.lowercased().contains(searchText.lowercased())
    })

    isFiltering = filteredData.count > 0
    self.tableView.reloadData()
}
Scriptable
  • 19,402
  • 5
  • 56
  • 72
  • 1
    Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/171330/discussion-on-answer-by-scriptable-searching-data-from-table-view-issue-in-swift). – Samuel Liew May 18 '18 at 13:10
  • bro i think issue is coming in this functionfunc searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) . In this line u have given ur array name as filteredNames = names.filter where names is ur array, but in my case my i have not any static array my data is coming from model class so how should i pass my model class here instead of names array? @Scriptable – raheem May 18 '18 at 13:11
  • i think if we pass the data coming from array it will be resolved. How can i pass my data coming from model. @Scriptable – raheem May 18 '18 at 13:16
  • you haven't mentioned a model in all of our conversation or your question... I cannot give you a solution with half of the information. add ALL of the information into the question if you want a solution. I cannot guess as to what data you have or how you have setup your model. so all this time we've been filtering an empty array?? – Scriptable May 18 '18 at 13:19
  • please check i have added my model class. @ Scriptable – raheem May 18 '18 at 15:00
  • what field do you want to filter by?, your array name is dishNameArray but I do not see dishNames in the model. I also do not see how you are getting any data into dishNameArray. you seem to be making it hard for me to give you an answer. each time I fix the issue you explain more of the problem. show the full details. – Scriptable May 18 '18 at 15:19
  • let me clear it to u. this is how i pass data to cell label in my table view cell class. cell.dishTitleLbl.text = ResMenuService.instance.categoryModelInstance[indexPath.row].categoryName. This is actual model where the data comes from ResMenuService.instance.categoryModelInstance . So this could be consider as an array which contains data and i think this should be pass to that place in filter. @Scriptable – raheem May 18 '18 at 15:25
  • have u got it? @Scriptable – raheem May 18 '18 at 15:25
  • Yes I think so. so you want to search category name yeah? Let me see how you can do it – Scriptable May 18 '18 at 15:28
  • yes i want this on search in table view. @Scriptable – raheem May 18 '18 at 15:31
  • It is easy, check Edit 3 in my answer. if you would of explain this in yoru question first I could of solved it 4 hours ago – Scriptable May 18 '18 at 15:34
  • Where this filterData coming from? @Scriptable – raheem May 18 '18 at 15:49
  • filterData is your thing. `var filteredData = [String]()` but its not string anymore its category model. so maybe [CategoryModel]()? what type do you have – Scriptable May 18 '18 at 15:53
  • 1
    Got it now after great struggle from u , bro big big thanks. :) . @ Scriptable – raheem May 18 '18 at 15:59
0

Try this

func updateSearchResults(for searchController: UISearchController) {

        guard let searchtext = searchController.searchBar.text else {
            return
        }
        filteredData.removeAll(keepingCapacity: false)

        let filterPredicate = NSPredicate(format: "self contains[c] %@", argumentArray: [searchtext])

        filteredData = dishNameArray.filter { filterPredicate.evaluate(with: $0) }
        print(filteredData)
        DispatchQueue.main.async {
            self.menuTableView.reloadData()
        }
}
iParesh
  • 2,338
  • 1
  • 18
  • 30
0

You need to add function to identify searching state as below:

func isSearching() -> Bool {
  return searchController.isActive && !searchBarIsEmpty()
}

func searchBarIsEmpty() -> Bool {
  // Returns true if the text is empty or nil
  return searchController.searchBar.text?.isEmpty ?? true
}
Sandip Patel - SM
  • 3,346
  • 29
  • 27