I am trying to follow-up with this tutorial on CodePath: Visit https://guides.codepath.com/ios/Search-Bar-Guide#cancelling-out-of-search-and-hiding-keyboard
I created a SearchViewController to search but then didSelectRowAt doesn't work
This is my code below:
class SearchViewController: UIViewController, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
let data = ["New York, NY", "Los Angeles, CA", "Chicago, IL", "Houston, TX",
"Philadelphia, PA", "Phoenix, AZ", "San Diego, CA", "San Antonio, TX",
"Dallas, TX", "Detroit, MI", "San Jose, CA", "Indianapolis, IN",
"Jacksonville, FL", "San Francisco, CA", "Columbus, OH", "Austin, TX",
"Memphis, TN", "Baltimore, MD", "Charlotte, ND", "Fort Worth, TX"]
var filteredData: [String]!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.allowsSelection = true
searchBar.delegate = self
filteredData = data
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell", for: indexPath) as UITableViewCell
cell.textLabel?.text = filteredData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
// THIS IS NOT WORKING
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("didSelectRowAt(\(indexPath)")
}
// This method updates filteredData based on the text in the Search Box
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// When there is no text, filteredData is the same as the original data
// When user has entered text into the search box
// Use the filter method to iterate over all items in the data array
// For each item, return true if the item should be included and false if the
// item should NOT be included
filteredData = searchText.isEmpty ? data : data!.filter { (item: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return item.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
}
tableView.reloadData()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
searchBar.text = ""
searchBar.resignFirstResponder()
}
}
Is there something else I need to do? The tutorial doesn't show any other code I was missing.
You should the UITableViewDelegate delegate to your class and tableView.delegate = self in your viewDidLoad function. This will help to trigger the delegates functions of the UITableViewController.
In Your viewDidLoad() method
tableView.delegate = self
tableView.dataSource = self
Related
I have a searchResultsViewController in my iOS application that displays an array of data for the user to be able to search through. When I try to search a random letter, lets say P for instance it does not show any of the words containing P.
the code that I used to create this searchResults is,
var array = ["Assembly", "Auto Care", "Electronic Help", "Item Delivery", "Handyman", "House Chores", "Junk Removal", "Lawn & Yard Care", "Moving", "Painting", "Pet Care", "Seasonal Work"]
var selectedItems = [String]()
var searchController = UISearchController()
var filteredArray = [String]()
var resultsController = UITableViewController()
override func viewDidLoad() {
super.viewDidLoad()
searchController = UISearchController(searchResultsController: resultsController)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
resultsController.tableView.delegate = self
resultsController.tableView.dataSource = self
searchController.searchBar.showsCancelButton = true
searchController.searchBar.showsScopeBar = true
searchController.searchBar.delegate = self
let attributes = [NSAttributedString.Key.foregroundColor: GREEN_Theme]
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(attributes, for: UIControl.State.normal)
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "Done"
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
done()
}
func updateSearchResults(for searchController: UISearchController) {
filteredArray = array.filter({ (array:String) -> Bool in
if array.contains(searchController.searchBar.text!) {
return true
} else {
return false
}
})
resultsController.tableView.reloadData()
searchController.automaticallyShowsCancelButton = true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedItems.contains(array[indexPath.row]) {
selectedItems.remove(at: selectedItems.firstIndex(of: array[indexPath.row])!)
tableView.cellForRow(at: indexPath)?.accessoryType = .none
} else {
selectedItems.append(array[indexPath.row])
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
tableView.reloadData()
print(selectedItems)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == resultsController.tableView {
return filteredArray.count
} else {
return array.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = array[indexPath.row]
if selectedItems.contains(array[indexPath.row]) {
cell.accessoryType = .checkmark
}else{
cell.accessoryType = .none
}
return cell
}
any thoughts?
Use this function to filter stuff:
extension SearchVC: UISearchBarDelegate{
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
fetchedData = []
if searchText == ""{
fetchedData = items
} else {
for words in items{
if
words.item.lowercased().contains(searchText.lowercased()){
filteredData.append(words)
}
}
}
table.reloadData()
}
}
Where fetchedData is an empty string array and items is your array.
If the search bar is empty fetchedData will be filled with all of your items, else just with the matched ones.
Now, the most important thing to do is to use fetchedData instead of items to display the results and the count properly. So, for instance:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return filteredData.count
}
Furthermore, as other users pointed out in the comments, you should really check your cellForRowAt. Try this link: https://stackoverflow.com/a/34345245/10408872
My Scenario, I am trying to Implement UITableview with custom cell and search option. Here, Search working fine but I cant able to select search result particular row. How to fix this also please check below my code is fine for search option Integration. If anything idea about code simplification, Please provide some sample.
My Code Below
class CustomCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
}
class ViewController: UIViewController,UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource {
var searchController : UISearchController!
let data = ["New York, NY", "Los Angeles, CA", "Chicago, IL", "Houston, TX",
"Philadelphia, PA", "Phoenix, AZ", "San Diego, CA", "San Antonio, TX",
"Dallas, TX", "Detroit, MI", "San Jose, CA", "Indianapolis, IN",
"Jacksonville, FL", "San Francisco, CA", "Columbus, OH", "Austin, TX",
"Memphis, TN", "Baltimore, MD", "Charlotte, ND", "Fort Worth, TX"]
var filteredData: [String]!
let cellReuseIdentifier = "cell"
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
filteredData = data
}
#IBAction func searchAction(_ sender: Any) {
// Create the search controller and specify that it should present its results in this same view
searchController = UISearchController(searchResultsController: nil)
// Set any properties (in this case, don't hide the nav bar and don't show the emoji keyboard option)
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.keyboardType = UIKeyboardType.asciiCapable
searchController.searchBar.barTintColor = #colorLiteral(red: 1, green: 0.5763723254, blue: 0, alpha: 1)
searchController.searchBar.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
// Make this class the delegate and present the search
self.searchController.searchBar.delegate = self
searchController.searchResultsUpdater = self
present(searchController, animated: true, completion: nil)
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.filteredData.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:CustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! CustomCell
cell.nameLabel.text = self.filteredData[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
print(cell.nameLabel?.text! ?? "")
//self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top)
}
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text {
filteredData = searchText.isEmpty ? data : data.filter({(dataString: String) -> Bool in
return dataString.range(of: searchText, options: .caseInsensitive) != nil
})
tableView.reloadData()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
Swift 5 (iOS 12+)
searchController.obscuresBackgroundDuringPresentation = false
The Problem is inside cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:CustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! CustomCell
cell.nameLabel.text = self.filteredData[indexPath.row]
return cell
}
Update Above Code with Following Code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:CustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as! CustomCell
cell.nameLabel.text = self.filteredData[indexPath.row]
return cell
}
We can solve this by using below code.
The problem is dimsBackgroundDuringPresentation
searchController.dimsBackgroundDuringPresentation = false
I started a table view with a list of universities and created a search bar to tag along with it. The search bar works but only if I type in the name of the school exactly how it is. Is there a way I can change the it to search any part of the name and get the same results? Here's the code that I have set up.
#IBOutlet weak var schoolSearch: UISearchBar!
#IBOutlet weak var tblView: UITableView!
let schoolnames = ["Long Beach City College LAC", "California State University, Bakersfield", ...]
var searchedSchool = [String]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
schoolSearch.delegate = self
self.tblView.delegate = self
self.tblView.reloadData()
}
extension ChooseSchool: UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchedSchool.count
} else {
return schoolnames.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell
cell?.img.image = UIImage(named: schoolnames[indexPath.row])
cell?.lbl.text = schoolnames[indexPath.row]
_ = tableView.dequeueReusableCell(withIdentifier: "cell")
if searching {
cell?.textLabel?.text = searchedSchool[indexPath.row]
} else {
cell?.textLabel?.text = schoolnames[indexPath.row]
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "TestController") as? TestController
vc?.schoolnames = schoolnames[indexPath.row]
navigationController?.pushViewController(vc!, animated: true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchedSchool = schoolnames.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
searching = true
tblView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
tblView.reloadData()
}
}
Replace
searchedSchool = schoolnames.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
with
searchedSchool = schoolnames.filter { $0.range(of: searchText, options: .caseInsensitive) != nil }
I think you have to make your searchBar implement the containsString method to achieve what you need. For reference look at this link
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var filteredStates = [State]()
var states = [
State(stateName: "Alabama", abbreviation: "AL" ),
State(stateName: "Alaska", abbreviation: "AK" ),
State(stateName: "Arizona", abbreviation: "AZ"),
State(stateName: "Arkansas", abbreviation: "AR"),
State(stateName: "California", abbreviation: "CA"),
State(stateName: "Colorado", abbreviation: "CO"),
State(stateName: "Connecticut", abbreviation: "CT"),
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
searchBar.delegate = self
filteredStates = states
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tableView.addGestureRecognizer(tap)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell", for: indexPath) as UITableViewCell
let state = states[indexPath.row]
cell.textLabel?.text = state.stateName
cell.detailTextLabel?.text = state.abbreviation
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredStates.count
}
// This method updates filteredData based on the text in the Search Box
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredStates = searchText.isEmpty ? states : states.filter { (item: State) -> Bool in
return item.stateName.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
}
print(filteredStates)
tableView.reloadData()
}
// Wehn search bar being editing, cancel button pops up
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
// When search bar cancel button clicked
// Cancel button dismiss, search text is empty, keyboard dismiss
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
searchBar.text = ""
searchBar.resignFirstResponder()
}
// when click outside of keyboard
// Keyboard dismiss
func handleTap() {
self.view.endEditing(true)
}
// When clicked search button
// keyboard dismiss
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
}
My filter is not working correctly. Not sure where I did wrong. I think it might be something wrong with function searchBar function.
The filter is not working correct
The filter is not working correct2
I have slightly changed your code, and with filter used contains and made some changes in delegate method of UISearchBar and used filteredStates with all the method of UITableViewDataSource.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var filteredStates = [State]()
var states = [
State(stateName: "Alabama", abbreviation: "AL" ),
State(stateName: "Alaska", abbreviation: "AK" ),
State(stateName: "Arizona", abbreviation: "AZ"),
State(stateName: "Arkansas", abbreviation: "AR"),
State(stateName: "California", abbreviation: "CA"),
State(stateName: "Colorado", abbreviation: "CO"),
State(stateName: "Connecticut", abbreviation: "CT"),
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
filteredStates = states
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredStates.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell")!
//Access data from filteredStates array not from states array
cell.textLabel?.text = self.filteredStates[indexPath.row].stateName
cell.detailTextLabel?.text = self.filteredStates[indexPath.row].abbreviation
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(self.filteredStates[indexPath.row].stateName)
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = true
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.filteredStates = searchText.isEmpty ? states : states.filter( { $0.stateName.localizedCaseInsensitiveContains(searchText) })
self.tableView.reloadData()
}
func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
searchBar.showsCancelButton = false
searchBar.text = ""
self.filteredStates = states
self.tableView.reloadData()
}
}
Note: I haven't added code that you are using with TapGesture so don't forgot to add that.
Create another array to store the searched value
Implement filter logic on your searchBar: textDidChange method
Create a Bool (eg. isActive) to indicate if the search happened or cancelled on searchBarShouldBeginEditing and searchBarCancelButtonClicked
On your cellForRow, you can check for the Bool isActive to switch the let state = states[indexPath.row] to let state = filteredStates[indexPath.row]
I add a search bar by adding subview into a UIView. When I tap the search bar, cancel button shows up, however the keyboard disappear immediately. I have to tap the search bar again so that I can input some text for searching.
Any thoughts?
Use the following code:
import UIKit
class ViewController: UIViewController,UISearchDisplayDelegate, UISearchBarDelegate,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var headingLabel: UILabel!
#IBOutlet weak var countriesTableView: UITableView!
#IBOutlet weak var countrySerachBar: UISearchBar!
var marrCountryList = [String]()
var marrFilteredCountryList = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.countriesTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
marrCountryList = ["USA", "Bahamas", "Brazil", "Canada", "Republic of China", "Cuba", "Egypt", "Fiji", "France", "Germany", "Iceland", "India", "Indonesia", "Jamaica", "Kenya", "Madagascar", "Mexico", "Nepal", "Oman", "Pakistan", "Poland", "Singapore", "Somalia", "Switzerland", "Turkey", "UAE", "Vatican City"]
self.countriesTableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.countrySerachBar.becomeFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if tableView == self.searchDisplayController!.searchResultsTableView {
return self.marrFilteredCountryList.count
} else {
return self.marrCountryList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cellCountry = self.countriesTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var countryName : String!
if tableView == self.searchDisplayController!.searchResultsTableView {
countryName = marrFilteredCountryList[(indexPath as NSIndexPath).row]
} else {
countryName = marrCountryList[(indexPath as NSIndexPath).row]
}
cellCountry.textLabel?.text = countryName
return cellCountry
}
func filterTableViewForEnterText(_ searchText: String) {
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchText)
let array = (self.marrCountryList as NSArray).filtered(using: searchPredicate)
self.marrFilteredCountryList = array as! [String]
self.countriesTableView.reloadData()
}
func searchDisplayController(_ controller: UISearchDisplayController, shouldReloadTableForSearch searchString: String?) -> Bool {
self.filterTableViewForEnterText(searchString!)
return true
}
func searchDisplayController(_ controller: UISearchDisplayController,
shouldReloadTableForSearchScope searchOption: Int) -> Bool {
self.filterTableViewForEnterText(self.searchDisplayController!.searchBar.text!)
return true
}
}
Storyboard screenshot:
Output:
Please check my GitHub link to test sample project:
https://github.com/k-sathireddy/SearchDisplayControllerSample