I am trying to add a UISearchBar to a UITableviewController that gets data from a db call. I added uisearchbar above the tableview in storyboard and made the outlet added delegate in class declaration and viewdidload. Code seems right but I am getting no reaction when typing in the searchClients function. Not sure what I am missing. No errors showing in console.
import UIKit
class TableViewController: UITableViewController, UISearchBarDelegate {
var CompanyID = ""
var CompanyName = ""
var ClientList = [Client]()
var filteredArray = [Client]()
let URL_SERVICE = "https://fetch.php"
#IBOutlet var searchClients: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
searchClients.delegate = self
filteredArray = ClientList
// omitted call to get data, as it loads fine initially
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Client", for: indexPath)
let client = filteredArray[indexPath.row]
let title = client.Name
cell.textLabel?.text = title
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.CompanyID = filteredArray[indexPath.row].CompanyID
self.CompanyName = filteredArray[indexPath.row].Name
}
func searchClients(_ searchBar: UISearchBar, textDidChange searchText: String) {
//let text: String = self.searchClients.text ?? ""
print("search for \(searchText)")
self.filteredArray = []
if (searchText == "") {
self.filteredArray = self.ClientList
} else {
for item in self.ClientList {
if (item.Name.lowercased().contains(searchText.lowercased())) {
self.filteredArray.append(item)
}
}
}
print(filteredArray)
self.tableView.reloadData()
}
}
What am I missing here that keeps searchBar function from firing?
You are not implementing any method from the UISearchBarDelegate since your searchClients function is not correctly named. You will need to rename it to searchBar to actually implement the function from the delegate protocol.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
Few Nitpicks
In Swift we are naming properties by starting with a lowercase letter
var companyID = ""
var companyName = ""
var clientList = [Client]()
var filteredArray = [Client]()
let urlService = "https://fetch.php"
You can skip every self. you are adding as a prefix when referencing to a property or function within the scope of the declaring type.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
companyID = filteredArray[indexPath.row].companyID
companyName = filteredArray[indexPath.row].name
}
Its much cleaner when protocol conformances are implemented using extensions so the code is nicely separated and they do not mix with each other.
extension TableViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//let text: String = self.searchClients.text ?? ""
print("search for \(searchText)")
filteredArray = []
if searchText == "" {
filteredArray = clientList
} else {
filteredArray = clientList.filter { item in
item.name.lowercased().contains(searchText.lowercased())
}
}
print(filteredArray)
tableView.reloadData()
}
}
Related
I have a table which has a name and a picture in each cell. I have a search bar which searches through the names which does successfully happen however the images are blank. When you erase your search from the search bar, the images in the cell also do disappear! Would anyone know what I have done wrong and if so can someone please help me out!
Thank you
Had an issue with when search is deleted images are not shown but now it is fixed thanks to Raja
Only issue left is that it does not filter images when searched. Images are still blank when the cells are searched
import UIKit
class TestTableViewController: UITableViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var userWorkoutName: UILabel!
var valueToPass: String!
var workoutName = ["Apple","Orange","Banana"]
var workoutImage = ["A","O","B"]
var searchingWorkouts = [String()]
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
searchingWorkouts = workoutName
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchingWorkouts.count
} else {
return workoutName.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIentifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIentifier, for: indexPath) as! WorkoutTableViewCell
if searching {
cell.workoutName.text = searchingWorkouts[indexPath.row]
cell.workoutImage.image = UIImage(named: searchingWorkouts[indexPath.row])
} else {
cell.workoutName.text = workoutName[indexPath.row]
cell.workoutImage.image = UIImage(named: workoutImage[indexPath.row])
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let moreDetail = storyboard.instantiateViewController(identifier: "UploadWorkoutViewController") as! UploadWorkoutViewController
if searching {
moreDetail.getWorkoutTitle = searchingWorkouts[indexPath.row]
} else {
moreDetail.getWorkoutTitle = workoutName[indexPath.row]
}
self.navigationController?.pushViewController(moreDetail, animated: true)
}
}
extension TestTableViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchingWorkouts = workoutName.filter({$0.prefix(searchText.count) == searchText } )
searching = true
if searchText.isEmpty {
searching = false
} else {
searching = true
}
tableView.reloadData()
}
}
Images are blank because you're not filtering the images, you're only filtering the workout names. And while searching you're assigning searchingWorkouts to image, which is totally wrong.
cell.workoutImage.image = UIImage(named: searchingWorkouts[indexPath.row])
Just like maintaining the searchingWorkouts, you need to maintain the searchingWorkoutImage as well. And then change the above line to this
cell.workoutImage.image = UIImage(named: searchingWorkoutImage[indexPath.row])
But the question is how will you filter the image names? Because workout names and image names are different.
So a better solution is to create a Workout class with name and image properties and change your code to the following
class Workout {
var name: String = ""
var image: String = ""
init(name: String, image: String) {
self.name = name
self.image = image
}
}
class TestTableViewController: UITableViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var userWorkoutName: UILabel!
var valueToPass: String!
var workouts = [Workout(name: "Apple", image: "A"), Workout(name: "Orange", image: "O")]
var searchingWorkouts = [Workout]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
searchingWorkouts = workouts
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchingWorkouts.count
} else {
return workouts.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIentifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIentifier, for: indexPath) as! WorkoutTableViewCell
if searching {
cell.workoutName.text = searchingWorkouts[indexPath.row].name
cell.workoutImage.image = UIImage(named: searchingWorkouts[indexPath.row].image)
} else {
cell.workoutName.text = workouts[indexPath.row].name
cell.workoutImage.image = UIImage(named: workouts[indexPath.row].image)
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let moreDetail = storyboard.instantiateViewController(identifier: "UploadWorkoutViewController") as! UploadWorkoutViewController
if searching {
moreDetail.getWorkoutTitle = searchingWorkouts[indexPath.row].name
} else {
moreDetail.getWorkoutTitle = workouts[indexPath.row].name
}
self.navigationController?.pushViewController(moreDetail, animated: true)
}
}
extension TestTableViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchingWorkouts = workouts.filter({$0.name.prefix(searchText.count) == searchText } )
searching = true
if searchText.isEmpty {
searching = false
} else {
searching = true
}
tableView.reloadData()
}
}
What I want to do: I want to filter the TableViewCells that have a text on them, depending on the user input in the UISearchBar.
My problem: I got it to work with simple Cells. But now I use custom ones that I configure with a struct and now I have no clue how to filter them.
Here is my code:
import UIKit
import Firebase
class searchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet var searchy: UISearchBar!
#IBOutlet var tabley: UITableView!
var searchingNames = [String()]
var searching = false
var datas = [UserSearch]()
override func viewDidLoad() {
super.viewDidLoad()
tabley.delegate = self
tabley.dataSource = self
searchy.delegate = self
populateSearch()
tabley.register(searchTableViewCell.nib(), forCellReuseIdentifier: searchTableViewCell.identifier)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching == true {
return searchingNames.count
} else{
return datas.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if searching == true {
let post = datas[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: searchTableViewCell.identifier, for: indexPath) as! searchTableViewCell
cell.configure(with: post)
return cell
} else{
let post = datas[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: searchTableViewCell.identifier, for: indexPath) as! searchTableViewCell
cell.configure(with: post)
return cell
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchingNames = datas.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()}) // Cannot assign value of type '[UserSearch]' to type '[String]'
// Value of type 'UserSearch' has no member 'lowercased'
searching = true
tabley.reloadData()
}
}
struct UserSearch {
var id: String
var name: String
var pfp: String
}
You have to filter a string property of UserSearch for example the name.
And you have to consider that the user clears the search field to reset searching to false
And there is a more efficient API to check for a substring
First of all the array of the search results must be the same type as the data source array
var searchingNames = [UserSearch]()
Replace textDidChange with
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
searchingNames.removeAll()
searching = false
} else {
searchingNames = datas.filter{ $0.name.range(of: searchText, options: [.anchored, .caseInsensitive]) != nil}
searching = true
}
tabley.reloadData()
}
.anchored searches from the beginning of the string
.caseInsensitive is self-explanatory.
PS: In cellForRow is a mistake. In the searching == true scope replace
let post = datas[indexPath.row]
with
let post = searchingNames[indexPath.row]
You can even reduce the code to
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = searching ? searchingNames[indexPath.row] : datas[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: searchTableViewCell.identifier, for: indexPath) as! searchTableViewCell
cell.configure(with: post)
return cell
}
There is table view to show phone contact and a search Bar in view controller . I implemented the code for the search-bar to filter givenName array it works just fine but when I click on the cell that I searched it doesn’t display the right information it displays only the information of the first row( exactly first index). The question it is how i can solve this problem ?
Data Model :
struct ContactStruct {
let identifier : String
let thumbnailImageData : UIImage
let givenName : String
let familyName : String
let phoneNumbers : String
let emailAddresses : String
}
Main View controller :
class NewContactViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var contactsData = [ContactStruct]()
var searchedContact = [String]()
var searching = false
#IBOutlet weak var tblMain: UITableView!
#IBOutlet weak var contactSearchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let story = UIStoryboard(name: "Main", bundle: nil)
let vc = story.instantiateViewController(withIdentifier: "InsertContactViewController") as! InsertContactViewController
vc.strEditFitstName = contactsData[indexPath.row].givenName
vc.stridentifier = contactsData[indexPath.row].identifier
self.navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchedContact.count
} else {
return contactsData.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testCell") as! NewContactCell
let contactToDisplay = contactsData[indexPath.row]
contactToDisplay.familyName
cell.lblLName.text = contactToDisplay.givenName
return cell
}
search bar methods:
extension NewContactViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let givenNames = contactsData.map { $0.givenName }
searchedContact = givenNames.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
searching = true
tblMain.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
tblMain.reloadData()
}
}
Follow just below code as an example :
let countriesList = ["African Union",
"Andorra",
"Armenia",
"Austria",
"Bahamas",
"Barbados",
"Belarus",
"Belgium",
]
#IBOutlet var tblPlace: UITableView!
#IBOutlet var searchPlace: UISearchBar!
var selctedCountries:[String] = []
var filteredData: [String]!
override func viewDidLoad() {
super.viewDidLoad()
searchPlace.delegate = self
filteredData = countriesList
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ApplyFilterTableViewCell", for: indexPath) as! ApplyFilterTableViewCell
cell.lblName = filteredData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let story = UIStoryboard(name: "Main", bundle: nil)
let vc = story.instantiateViewController(withIdentifier: "InsertContactViewController") as! InsertContactViewController
vc.strEditFitstName = filteredData[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = searchText.isEmpty ? countriesList : countriesList.filter({(dataString: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return dataString.range(of: searchText, options: .caseInsensitive) != nil
})
tblPlace.reloadData()
}
I want to search for some data in the table view using the search bar, but when I try to find data in my model, I'm not able to search that data.I made a expand table view cell and created a search bar for searching data, but still I can't search the data in the model. How can I achieve that?
here is my code:
import UIKit
class FAQViewController: UIViewController, UITableViewDataSource {
var dataFaq = [modelFAQ]()
let items = [
modelFAQ(name: "1. search box", description: "The design led users to instinctively search for their question first before clicking the FAQs"),
modelFAQ(name: "2.list of FAQs ", description: "Customers clicked around on the FAQs first."),
modelFAQ(name: "3. customers", description: "top issues first and then use the search for the questions instead of browsing and yielding more relevant results")
]
#IBOutlet fileprivate weak var tableView: UITableView!
#IBOutlet weak var searchDataBar: UISearchBar!
fileprivate var indexPaths: Set<IndexPath> = []
var cellIdentifier = "dataSourceFAQ"
var searchData = [String]()
var searching = false
override var preferredStatusBarStyle: UIStatusBarStyle{
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
searchDataBar.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchData.count
}else {
return items.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! FAQTableViewCell
if searching{
cell.titleLabel.text = searchData[indexPath.row]
}else{
cell.titleLabel.text = items[indexPath.row].name
}
let nameDetail = self[indexPath].name as? String
let description = self[indexPath].description
cell.update(name: nameDetail ?? "0", description: description)
cell.state = cellIsExpanded(at: indexPath) ? .expanded : .collapsed
return cell
}
override func viewWillAppear(_ animated: Bool) {
tabBarController?.tabBar.isHidden = true
}
private func setupTableView(){
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200.0
tableView.separatorStyle = .none
}
}
extension FAQViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as!FAQTableViewCell
cell.state = .expanded
self.addExpandedIndexPath(indexPath)
tableView.beginUpdates()
tableView.endUpdates()
print("1")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! FAQTableViewCell
cell.state = .collapsed
self.removeExpandedIndexPath(indexPath)
tableView.beginUpdates()
tableView.endUpdates()
print("2")
}
}
extension FAQViewController {
func cellIsExpanded(at indexPath: IndexPath) -> Bool {
return indexPaths.contains(indexPath)
}
func addExpandedIndexPath(_ indexPath: IndexPath) {
indexPaths.insert(indexPath)
}
func removeExpandedIndexPath(_ indexPath: IndexPath) {
indexPaths.remove(indexPath)
}
}
extension FAQViewController {
subscript(indexPath: IndexPath) -> modelFAQ {
return items[indexPath.row]
}
}
extension FAQViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchData = searchText.isEmpty ? items: items.filter{ $0.name(searchText)}
searching = true
tableView.reloadData()
}
}
here is my table view cell
import UIKit
class FAQTableViewCell: UITableViewCell {
enum cellState{
case collapsed
case expanded
var carretImage: UIImage {
switch self {
case .collapsed:
return UIImage(named: "ic_arrow_down")!
case .expanded:
return UIImage(named: "ic_arrow_up")!
}
}
}
#IBOutlet private weak var stackView: UIStackView!
#IBOutlet private weak var containerView: UIView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet private weak var carret: UIImageView!
#IBOutlet private weak var descriptionLabel: UILabel!
private let expandedViewIndex: Int = 1
var state: cellState = .expanded {
didSet{
toggle()
}
}
override func awakeFromNib() {
selectionStyle = .none
containerView.layer.cornerRadius = 5.0
}
private func toggle(){
stackView.arrangedSubviews[expandedViewIndex].isHidden = stateIsCollapsed()
carret.image = state.carretImage
}
private func stateIsCollapsed() -> Bool{
return state == .collapsed
}
func update(name: String, description: String){
titleLabel.text = name
descriptionLabel.text = description
}
}
here is my model
struct modelFAQ {
var name: String
var description: String
}
Two issues:
You have to declare searchData as the same type as the main data.
By the way according to the naming convention struct and class names start with a capital letter
var searchData = [ModelFAQ]()
The search filter closure is wrong. Write
searchData = searchText.isEmpty ? items : items.filter{ $0.name.contains(searchText)}
or if you want to search case insensitive
searchData = searchText.isEmpty ? items : items.filter{ $0.name.range(of: searchText, options: .caseInsensitive) != nil }
And you have to change in cellForRowAt
cell.titleLabel.text = searchData[indexPath.row].name
NOTE: Always start class/struct name with CAPITAL LETTER
Wrong: modelFAQ
correct: ModelFAQ
You have made one mistake, you have to declare your searchData Array like below
var searchData = [ModelFAQ]()
and while in datasource method you have to get name from the ModelFAQ object and assign it to your label.
Hope it will help you.
I am trying to use UISearchBar with Firebase but I get an error when I try to type any right word
My Code is
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var isSearching: Bool = false
//list to store all the artist
var hotelList = [hotelsModel]()
var filterHotels = [hotelsModel]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching{
return filterHotels.count
} else {
return hotelList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//creating a cell using the custom class
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! hotelsTableViewCell
//the artist object
let hotel: hotelsModel
//getting the artist of selected position
hotel = hotelList[indexPath.row]
//adding values to labels
cell.lblName.text = hotel.name
cell.lblLocation.text = hotel.location
cell.appSuggest.text = hotel.appSuggest
cell.price.text = hotel.price
cell.canceletion.text = hotel.cancelation
cell.paymentNeeded.text = hotel.paymentNeeded
if isSearching{
cell.lblName?.text = filterHotels[indexPath.row].name
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
isSearching = false
view.endEditing(true)
tableView.reloadData()
} else {
isSearching = true
filterHotels = hotelList.filter({$0.name == searchBar.text})
tableView.reloadData()
}
}
in this line
if isSearching{
cell.lblName?.text = filterHotels[indexPath.row].name
}
this's my code files
if someone can check it
https://github.com/HaMaDaRaOuF/UISearchBar-Firabse
thank you
I will suggest you to change the filter process (smth like this):
func filterContentForSearchText(_ searchText: String) {
let pred = NSPredicate(format: "name contains[cd] %#", searchText)
filteredHotels = hotelList?.filtered(using: pred) as? [hotelsModel]
tableView.reloadData()
}
and change the isSearching variable to this:
var isSearching: Bool {
return searchBar.text != ""
}
Use the debugger to see the indexPath.row value on the line that is causing your crash.
Thank You guys my problem it's was in the Number Of Rows In Section Function it's haven't return for filterHotels I edit my question if someone need the code also i'll edit it in github
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if isSearching{ return filterHotels.count } else { return hotelList.count } }
Github Link
https://github.com/HaMaDaRaOuF/UISearchBar-Firabse