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
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
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}