Search code examples
swiftsearchbar

Swift sorting result set depending upon the no of occurrences of string


hello i'm trying to implement search bar in swift 3.0,it's partially done, i'm not getting the expected results ,here is the thing i'm getting the results like this

enter image description here

here is my controller

     class MasterViewController: UITableViewController
      {
     // MARK: - Properties
      var detailViewController: DetailViewController? = nil
     var candies = [Candy]()
     var filteredCandies = [Candy]()
     let searchController = UISearchController(searchResultsController: nil)

// MARK: - Viewcontroller lifecycle
override func viewDidLoad()
{
    super.viewDidLoad()
    setupSearchVC()
    setupModelData()
    if let splitViewController = splitViewController {
        let controllers = splitViewController.viewControllers
        detailViewController = (controllers[controllers.count - 1] as! UINavigationController).topViewController as? DetailViewController
    }
}

override func viewWillAppear(_ animated: Bool)
{
    clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed
    super.viewWillAppear(animated)
}

// MARK: - View setup
func setupSearchVC()
{
    searchController.searchResultsUpdater = self
    searchController.dimsBackgroundDuringPresentation = false
    searchController.searchBar.scopeButtonTitles = ["All", "Chocolate", "Hard", "Other"]
    searchController.searchBar.delegate = self
    definesPresentationContext = true
    tableView.tableHeaderView = searchController.searchBar
}


// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
    if segue.identifier == "showDetail"
    {
        if let indexPath = tableView.indexPathForSelectedRow
        {
            let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
            controller.detailCandy = getDaCorrectCandyNow(row: indexPath.row)
            controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
            controller.navigationItem.leftItemsSupplementBackButton = true
        }
    }
}

// MARK: - Controller responsibilities
func setupModelData()
{
    candies = [
        Candy(category:"complete", name:"test"),
        Candy(category:"incomplete", name:"test test"),

    ]
}


func getDaCorrectCandyNow(row: Int) -> Candy
{
    let candy: Candy
    if searchController.isActive {
        candy = filteredCandies[row]
    } else {
        candy = candies[row]
    }
    return candy
}

func filterContentForSearchText(_ searchText: String, scope: String = "All")
{
    filteredCandies = candies.filter { candy in
        let categoryMatch = (scope == "All" ? true : (candy.category == scope))
        let searchValueIsNotEmpty = (categoryMatch && candy.name.lowercased().contains(searchText.lowercased()) || candy.category.lowercased().contains(searchText.lowercased()))
        let searchValueIsEmpty = (searchText == "")
        return searchValueIsEmpty ? categoryMatch : searchValueIsNotEmpty
    }
    tableView.reloadData()
    }
    }

here is my tableview

        override func tableView(_ tableView: UITableView, 
       numberOfRowsInSection section: Int) -> Int
           {
          if searchController.isActive {
         return filteredCandies.count
          }
    return candies.count
}



func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath)
    let candy: Candy = getDaCorrectCandyNow(row: indexPath.row)
    cell.textLabel?.text = candy.name
    cell.detailTextLabel?.text = candy.category
    return cell
}


// MARK: - Search bar Delegate
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int)
{
    print("*searchBar - ")
    filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}

// MARK: - Search View Controller Delegate
public func updateSearchResults(for searchController: UISearchController)
{
    let searchBar = searchController.searchBar
    print("*updateSearchResults - \(searchBar.text)")
    let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
    filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
 }

i want my results to be displayed on the basis of no. of occurrences in my result set for e.g if i search "test" in a search bar and if i my result set like this set candies = [ Candy(category:"complete", name:"test"), Candy(category:"incomplete", name:"test test")] i want Candy(category:"incomplete", name:"test test") to display first since it has two "test" items how do i do that? sorry for my english, plese help, thanks in advance


Solution

  • What you need to do is compute the number of matches on each candy and sort the data before you return it to your filteredCandies array. To achieve that, you'll need to modify your filter function.

    You can do it using the map() function to return an array of tuples combining the candy and match count.

    From there you can filter on the number of matches and sort the tuple array on the count. Then you simply remove the count from the tuple and return only the candy in the final array.

    To compute the number of matches, you can use the components() function of the String type. Using your search string as the separator for components() will return one more substring than the number of matches (so count - 1 will be the number of matches).

    Here's an example of how you could write the filter using that approach:

    filteredCandies = candies.map 
    { 
        candy in
    
        let matchesCategory = scope == "All" || candy.category == scope
        var matchCount      = matchesCategory ? 1 : 0
    
        if matchesCategory && searchText != ""
        {
            let lowerSearch = searchText.lowercased()
            matchCount  = candy.name.lowercased().components(separatedBy: lowerSearch).count - 1
            matchCount += candy.category.lowercased().components(separatedBy: lowerSearch).count - 1
        }
    
        return (candy,matchCount)
    }
    .filter{ $1 > 0 }.sorted{$0.1 > $1.1}.map{$0.0}