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
Related
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()
}
}
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()
}
}
In project i am getting all my contacts.. here i need to search contacts by their name how to do that
i have done almost but unable to filter in textDidChange
below is my tried code:
class ContactsViewController1: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var joinersTableView: UITableView!
var contacts = [CNContact]()
var search = false
var searchArray = [CNContact]()
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return jsonArrayTagged.count
} else {
if search {
return searchArray.count
} else {
return contacts.count
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 1 {
var cell1: ContactsTableViewCell2 = tableView.dequeueReusableCell(withIdentifier: "ContactsTableViewCell2", for: indexPath) as! ContactsTableViewCell2
if search {
cell1.nameLbl.text = searchArray[indexPath.row].givenName + " " + searchArray[indexPath.row].familyName
cell1.empRoleLbl.text = searchArray[indexPath.row].phoneNumbers.first?.value.stringValue
cell1.inviteButn.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
} else {
cell1.nameLbl.text = contacts[indexPath.row].givenName + " " + contacts[indexPath.row].familyName
cell1.empRoleLbl.text = contacts[indexPath.row].phoneNumbers.first?.value.stringValue
cell1.inviteButn.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
}
return cell1
}
return UITableViewCell()
}
}
extension ContactsViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchArray = contacts.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
search = true
joinersTableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
search = false
searchBar.text = ""
joinersTableView.reloadData()
}
}
error:
Value of type 'CNContact' has no member 'lowercased'
You can't just use a CNContact as a String and compare it with a String. You need to specify which String property of the CNContact you want to filter.
If you want to search the familyName for instance, do $0.familyName.lowerCased() instead of $0.lowerCased, since $0 is a CNContact.
extension ContactsViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchArray = contacts.filter {$0.familyName.lowercased().prefix(searchText.count) == searchText.lowercased()}
search = true
joinersTableView.reloadData()
}
...
}
Unrelated to your question, but why are you searching the beginning of the text only? Using localizedCaseInsensitiveContains instead of prefix would yield a much better user experience.
You don't need to use search: Bool.
Try these code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return jsonArrayTagged.count
} else {
return searchArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 1 {
var cell1: ContactsTableViewCell2 = tableView.dequeueReusableCell(withIdentifier: "ContactsTableViewCell2", for: indexPath) as! ContactsTableViewCell2
cell1.nameLbl.text = searchArray[indexPath.row].givenName + " " + searchArray[indexPath.row].familyName
cell1.empRoleLbl.text = searchArray[indexPath.row].phoneNumbers.first?.value.stringValue
cell1.inviteButn.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
return cell1
}
return UITableViewCell()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty{
searchArray = contacts
joinersTableView.reloadData()
}else{
searchArray = contacts.filter({$0.familyName.lowercased().contains(searchText.lowercased()) || $0.middleName.lowercased().contains(searchText.lowercased()) || $0.givenName.lowercased().contains(searchText.lowercased())})
joinersTableView.reloadData()
}
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
searchArray = contacts
joinersTableView.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
}
I have a little problem. I can search in a array via searchbar. But when I search a city not in my array, doesn't change anything. I want to show error message this case. For example 'Not found any city'
My code is below:
var cities = [String]()
var citiesFiltered = [String]()
var searchBarActive:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
cities = ["Abu Dabi","Amman","Berlin","Catarman","Dortmund"]
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if(searchBarActive){
return citiesFiltered.count
}
return cities.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if(searchBarActive){
cell.textLabel?.text = citiesFiltered[indexPath.row]
}else{
cell.textLabel?.text = cities[indexPath.row]
}
return cell
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBarActive = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBarActive = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBarActive = false
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBarActive = false
}
func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) {
searchBarActive = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
citiesFiltered = cities.filter({ (text) -> Bool in
let txt : NSString = text as NSString
let range = txt.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(citiesFiltered.count == 0){
searchBarActive = false
}else{
searchBarActive = true
}
self.tableView.reloadData()
}
//Try with this changes.
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if(searchBarActive && citiesFiltered.count == 0){
return 1
}
else if searchBarActive {
return citiesFiltered.count
}
return cities.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if(searchBarActive && citiesFiltered.count == 0){
cell.textLabel?.text = "Not found any city"
}
else if searchBarActive {
cell.textLabel?.text = citiesFiltered[indexPath.row]
}
else{
cell.textLabel?.text = cities[indexPath.row]
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
citiesFiltered = cities.filter({ (text) -> Bool in
let txt : NSString = text as NSString
let range = txt.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
self.tableView.reloadData()
}
The problem is here
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
citiesFiltered = cities.filter({ (text) -> Bool in
let txt : NSString = text as NSString
let range = txt.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(citiesFiltered.count == 0){
searchBarActive = false
}else{
searchBarActive = true
}
self.tableView.reloadData()
}
You are setting the bar as inactive, hence displaying the unfiltered list, when the filtered array is empty.
By removing
if(citiesFiltered.count == 0){
searchBarActive = false
}else{
searchBarActive = true
}
You'll get an empty table when searching for something that is not on the list.