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.
Related
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()
}
I am trying to grab the information from cell supplied while searching and bring it back to prior view controller. Here is my code:
//variables:
var searching = false
var cityArray = ["New York City, NY", "LA, CA"]
var searchCity = [String()]
//setup
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchCity.count
} else {
return cityArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CityTableViewCell", for: indexPath) as!
CityTableViewCell
if searching {
cell.cityLabel.text = searchCity[indexPath.row]
} else {
cell.cityLabel.text = cityArray[indexPath.row]
}
return cell
}
//tells what do once selecting the tableviewcell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if searching {
//This is the problem. I am telling to take the search city IndexPath.row
currentCity = searchCity[indexPath.row]
} else {
currentCity = cityArray[indexPath.row]
}
//currentCity is then used to go back to prev. VC and tell it what city was selected
if let presentingVC = presentingViewController as? DealsViewController {
if presentingVC.city != self.currentCity {
presentingVC.city = self.currentCity
presentingVC.deleteAllDeals()
presentingVC.noDeals = false
presentingVC.cityOutlet.setTitle(self.currentCity,for: .normal)
DispatchQueue.main.async {
presentingVC.getDealInfo {
presentingVC.DealsTableView.reloadData()
presentingVC.DealsTableView.layoutIfNeeded()
}
}
}
}
searchBar.text = currentCity
searching = false
tableView.deselectRow(at: indexPath, animated: true)
self.dismiss(animated: true, completion: nil)
searchBar.resignFirstResponder()
}
//searchBar
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchCity = cityArray.filter({$0.prefix(searchText.count) == searchText})
searching = true
reloadData()
}
Unfortunately, if it is in searching mode, the city returned does not change. It is the same as in previous view controller. I am looking to grab the exact indexpath.row from searchCity.
Thanks!
Figured this out:
There was a transcription error
to change it to lowercase:
searchCity = cityArray.filter({$0.lowercased().prefix(searchText.count) ==
searchText.lowercased()})
Thanks!
I'm trying to accomplish searching for other users only when the user has typed at least 2 words, and only then begin the search in the database (because I don't want to scan the entire database for the users). I had some problems with 2 letter search, but I think I got the code (thanks to user Jay).
However when I run it in the simulator, the console prints name, but nothing shows up in the tableview? (its empty).
Do you know what I have done wrong?
This is my code:
class FollowUsersTableViewController: UIViewController {
#IBOutlet var tableView: UITableView!
private var viewIsHiddenObserver: NSKeyValueObservation?
let searchController = UISearchController(searchResultsController: nil)
var usersArray = [UserModel]()
var filteredUsers = [UserModel]()
var loggedInUser: User?
//
var databaseRef = Database.database().reference()
//usikker på den koden over
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchBar.delegate = self
//large title
self.title = "Discover"
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.prefersLargeTitles = true
} else {
// Fallback on earlier versions
}
self.tableView?.delegate = self
self.tableView?.dataSource = self
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
self.searchController.delegate = self;
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
}
func searchUsers(text: String) {
if text.count >= 2 {
self.usersArray = [] //clear the array each time
let endingText = text + "\u{f8ff}"
databaseRef.child("profile").queryOrdered(byChild: "username")
.queryStarting(atValue: text)
.queryEnding(atValue: endingText)
.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let childSnap = child as! DataSnapshot
print(childSnap)
let userObj = Mapper<UserModel>().map(JSONObject: childSnap.value!)
userObj?.uid = childSnap.key
if childSnap.key != self.loggedInUser?.uid { //ignore this user
self.usersArray.append(userObj!)
}
}
self.tableView.reloadData()
})
}
} //may need an else statement here to clear the array when there is no text
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let dest = segue.destination as! UserProfileViewController
let obj = sender as! UserModel
let dict = ["uid": obj.uid!, "username": obj.username!, "photoURL": obj.photoURL, "bio": obj.bio]
dest.selectedUser = dict as [String : Any]
}
}
// MARK: - tableview methods
extension FollowUsersTableViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchController.searchBar.text!.count >= 2 ? filteredUsers.count : 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! FollowTableViewCell
let user = filteredUsers[indexPath.row]
cell.title?.text = user.username
if let url = URL(string: user.photoURL ?? "") {
cell.userImage?.sd_setImage(with: url, placeholderImage: #imageLiteral(resourceName: "user_male"), options: .progressiveDownload, completed: nil)
cell.userImage.sd_setIndicatorStyle(.gray)
cell.userImage.sd_showActivityIndicatorView()
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "user", sender: self.filteredUsers[indexPath.row])
}
}
// MARK: - search methods
extension FollowUsersTableViewController:UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate {
func updateSearchResults(for searchController: UISearchController) {
searchController.searchResultsController?.view.isHidden = false
self.searchUsers(text: self.searchController.searchBar.text!)
filterContent(searchText: self.searchController.searchBar.text!)
self.tableView.reloadData()
}
func filterContent(searchText:String){
if searchText.count >= 2{
self.filteredUsers = self.usersArray.filter{ user in
return(user.username!.lowercased().contains(searchText.lowercased()))
}
}
}
}
This code is very close, and as the other answer points out you've got two arrays.
But let's really simplify the issue: You only need one array since you are filtering Firebase, not the array.
In other words when a user types into the searchField, you're querying Firebase for results and putting them into an array. That's the same array you should be using as the dataSource for your tableView.
As your code sits, you're filtering Firebase and then filtering those results again which isn't needed.
So at a high level supposed we have four users in our database
Larry
Moe
Monroe
Curly
the user types 'Mo' into the search field which causes your Fire query to execute. It will return two results:
Mo
Monroe
which then gets populated into an array - we'll call it userResultsArray.
then
tableView.reloadData()
which then calls the tableView delegate methods
func tableView(_ tableView: UITableView, numberOfRowsInSection
return userResultsArray.count
and
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
get the user from userResultsArray
return the user name
As shown, you only need the single array to store the queried (filtered) results from Firebase.
You have 2 arrays usersArray and filteredUsers
if childSnap.key != self.loggedInUser?.uid { //ignore this user
self.usersArray.append(userObj!)
}
...
self.tableView.reloadData()
so the the above reload has no effect as you always use
let user = filteredUsers[indexPath.row]
in cellForRowAt , in such cases you should have a var like
var isSearching = false
and alter it when you search then in all delegate & dataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return isSearching ? filteredUsers.count : usersArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! FollowTableViewCell
let user = isSearching ? filteredUsers[indexPath.row] : usersArray[indexPath.row]
.....
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
isSearching = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
isSearching = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isSearching = false
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
isSearching = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredUsers = usersArray.filter { /* do filter */ }
if(filteredUsers.count == 0){
isSearching = false
} else {
isSearching = true
}
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "user", sender: isSearching ? self.filteredUsers[indexPath.row] : self.usersArray[indexPath.row])
}
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
I've referenced https://shrikar.com/swift-ios-tutorial-uisearchbar-and-uisearchbardelegate/ to follow how searchBar works. I have followed to the tee, but when typing something in the searchBar, it is not filtering the results. Here is the code. Can you please help? Thanks.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var searchActive : Bool = false
var data = ["San Francisco","New York","San Jose","Chicago","Los Angeles","Austin","Seattle"]
var filtered:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
/* Setup delegates */
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
}
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
searchActive = true;
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filtered = data.filter({ (text) -> Bool in
let tmp: NSString = text as NSString
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchActive) {
return filtered.count
}
return data.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! UITableViewCell;
if(searchActive){
cell.textLabel?.text = filtered[indexPath.row]
} else {
cell.textLabel?.text = data[indexPath.row];
}
return cell;
}
}
I found out my answer to this. I had to delete the bindings for the IBOutlet for searchBar and tableView. Then I had to re-add them again. Not sure why it's like this, but it definitely worked.