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])
}
Related
I have a searchfield in my app, where users can search for other users. However i have three issues with it:
right now the search only works for users that have a uppercase username. So for instance when i search "Ma", the only user that is showing is "Makelele" because his username has a uppercase letter as the first letter. For instance "mattgilbert" isnt showing at all, even though i should be on the tableview.
when the user has appered in the tableview, the user is still in the tableview, even though i have canceled the search.
i have it so the search for users only begins when the user has typed atleast 2 words, however if a user types more than that into the searchfield the user will just dissapear in the tableview. And i have to search more letters in order for the user to appear again.
It would mean alot if someone could help me out on this, as i have used alot of time on this search stuff. I want it to be as cost effiecent as possible, and at the same time be scaleable (hence why i have this two letters atleast typed before the database runs its query)
This is my code:
class FollowUsersTableViewController: UIViewController {
#IBOutlet var tableView: UITableView!
var isSearching = false
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
//self.loadProfileData()
//self.searchBar(searchController.searchBar, textDidChange: searchController.searchBar.text)
}
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 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]
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: isSearching ? self.filteredUsers[indexPath.row] : self.usersArray[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()))
}
}
}
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) {
self.filteredUsers = self.usersArray.filter{ user in
return(user.username!.lowercased().contains(searchText.lowercased()))
}
if(filteredUsers.count == 0){
isSearching = false
} else {
isSearching = true
}
self.tableView.reloadData()
}
}
EDIT: Updated Code
class FollowUsersTableViewController: UIViewController {
#IBOutlet var tableView: UITableView!
var isSearching = false
private var viewIsHiddenObserver: NSKeyValueObservation?
let searchController = UISearchController(searchResultsController: nil)
var usersArray = [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
//self.loadProfileData()
//self.searchBar(searchController.searchBar, textDidChange: searchController.searchBar.text)
}
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 usersArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! FollowTableViewCell
let user = usersArray[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.usersArray[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()))
}
}
}
*/
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
isSearching = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
isSearching = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isSearching = false
self.usersArray = []
self.tableView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
isSearching = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.usersArray.filter{ user in
return(user.username!.lowercased().contains(searchText.lowercased()))
}
if(usersArray.count == 0){
isSearching = false
} else {
isSearching = true
}
self.tableView.reloadData()
}
}
Please try this It may helps you.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let filteredArray = usersArray.filter { ($0["username"] as! String).range(of: searchText!, options: [.diacriticInsensitive, .caseInsensitive]) != nil }
}
It may helps you.Thank you
The answer to question #1
1) right now the search only works for users that have a uppercase
username. So for instance when i search "Ma", the only user that is
showing is "Makelele" because his username has a uppercase letter as
the first letter. For instance "mattgilbert" isnt showing at all, even
though i should be on the tableview.
Firebase doesn't have any kind of uppercase or lowercase search. It just has a literal string query that's case sensitive
The typical solution is to store a 'display' version of your strings and then a 'search' version of your strings were the string is lowercased in code before storing it in Firbease. So for example, here's a users node
users
uid_0
display_name: "Kirk"
search_name: "kirk"
uid_1
display_name: "Spock"
search_name: "spock"
uid_1
display_name: "Montgomery Scott"
search_name: "montgomery scott"
2 when the user has appered in the tableview, the user is still in the
tableview, even though i have canceled the search.
If a search is canceled, the dataSource array should be cleared and your tableView reloaded.
myDataSourceArray = []
self.reloadTableview
3 i have it so the search for users only begins when the user has
typed atleast 2 words, however if a user types more than that into the
searchfield the user will just dissapear in the tableview. And i have
to search more letters in order for the user to appear again.
The code I posted in previous answers solves this and is a complete, tested solution. Please refer back to those answers - you can almost copy and paste the code into a project and it will work.
i made a SearchViewController that searches for users, but then I started adding it so the tableview doesn't display until the user has typed at least two characters. For some odd reason, nothing shows up in the tableview when I type in two words.
Does anyone know why?
class FollowUsersTableViewController: UIViewController, UISearchBarDelegate {
#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()
//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
//self.loadProfileData()
//self.searchBar(searchController.searchBar, textDidChange: searchController.searchBar.text)
}
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
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 {
func updateSearchResults(for searchController: UISearchController) {
searchController.searchResultsController?.view.isHidden = false
filterContent(searchText: self.searchController.searchBar.text!)
searchUsers(text: 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()))
}
}
}
}
Also worth to mention:
I didn't create the searchcontroller using storyboards, I did it programmatically.
I got the error saying
firebase [MC] Invalidating cache
before I added .indexOn in my Firebase rules, now it doesn't show up anymore - weird.
i have had this issue for a long time. The only way my searchUsers function work is when i put it in ViewDidLoad, and then change the .count to be equal or less than 0 (because its 0 by default).
But as it is right now (how i think it should work), the search dosent work.
What i want it to do is :
Only start the firebase search for users when the user has typed at least two letters (so it only searches for the users when the user has started typing, and only showing and loading the relevant data).
Also update the tableview along with filtering the search.
This is my code:
import UIKit
import FirebaseDatabase
import Firebase
import SDWebImage
import ObjectMapper
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()
//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
//self.loadProfileData()
//self.searchBar(searchController.searchBar, textDidChange: searchController.searchBar.text)
}
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 searchBar(_ searchBar: UISearchBar,
textDidChange searchText: String) {
self.searchUsers(text: searchText)
}
func updateSearchResults(for searchController: UISearchController) {
searchController.searchResultsController?.view.isHidden = false
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()))
}
}
}
}
You need to set
searchController.searchBar.delegate = self
You conform to UISearchResultsUpdating and implement updateSearchResults but it doesn't call func searchUsers(text: String)
and conform to UISearchControllerDelegate that has no relation to the search look to it's methods Here
Disparately asking for your assistance
I am trying to filter Table View using search bar but the data I am getting is not in the correct position,
I tried several times to figure it out but without any chance, the result I am getting is only the first row does not matter which Room Number I am typing
I pasted the code below, your assistance is highly appreciated
final let urlString = "http://ccm-hotels.com/ccmandroid/api/getteams.php"
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
#IBAction func BackToMenu(_ sender: UIBarButtonItem) {
let MainMenu = self.storyboard?.instantiateViewController(withIdentifier: "MainMenu") as! MainMenu
self.navigationController?.pushViewController(MainMenu, animated: true)
}
var openCaseRoomArray: [String] = []
var openCaseNameArray: [String] = []
var openCaseRoomArrayF: [String] = []
var inSearchMode = false
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
openCaseRoomArrayF = openCaseRoomArray
let alertController = UIAlertController(title: nil, message: "Please wait\n\n", preferredStyle: .alert)
let spinnerIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
spinnerIndicator.center = CGPoint(x: 135.0, y: 65.5)
spinnerIndicator.color = UIColor.black
spinnerIndicator.startAnimating()
alertController.view.addSubview(spinnerIndicator)
self.present(alertController, animated: false, completion: nil)
let when = DispatchTime.now() + 5
DispatchQueue.main.asyncAfter(deadline: when){
// your code with delay
alertController.dismiss(animated: true, completion: nil);}
self.downloadJsonWithURL()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func downloadJsonWithURL() {
let url=URL(string:"http://ccm-hotels.com/ccmandroid/api/getteams.php")
do {
let allContactsData = try Data(contentsOf: url!)
let allContacts = try JSONSerialization.jsonObject(with: allContactsData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String : AnyObject]
if let arrJSON = allContacts["CurrentOpenCases"] {
for index in 0...arrJSON.count-1 {
let aObject = arrJSON[index] as! [String : AnyObject]
if let Room = aObject["RoomNumber"] as? String {
openCaseRoomArray.append(Room)
}
if let Name = aObject["GuestName"] as? String {
openCaseNameArray.append(Name)
}
}
}
self.tableView.reloadData()
}
catch {
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if inSearchMode {
return openCaseRoomArrayF.count
}
return openCaseRoomArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
if inSearchMode {
cell.openCaseRoom.text = self.openCaseRoomArrayF[indexPath.row]
cell.openCaseName.text = self.openCaseNameArray[indexPath.row]
}else{
cell.openCaseRoom.text = self.openCaseRoomArray[indexPath.row]
cell.openCaseName.text = self.openCaseNameArray[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
vc.dopenCaseRoomString = openCaseRoomArray[indexPath.row]
vc.openCaseNameString = openCaseNameArray[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
view.endEditing(true)
tableView.reloadData()
} else {
inSearchMode = true
openCaseRoomArrayF = openCaseRoomArray.filter({$0 == searchBar.text})
tableView.reloadData()
}
}
}
In your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method you are showing name from self.openCaseNameArray while you are updating your search result in self.openCaseRoomArrayF. When you are getting the api response you are assigning room and its corresponding name in your two arrays but when you are searching and updating your search result in self.openCaseRoomArrayF the index get changed. Now openCaseNameArray and openCaseRoomArray are in matched index but openCaseNameArray and openCaseRoomArrayF are not in same matched index. So you will not get the corresponding name in openCaseNameArray if you take room from openCaseRoomArrayF.
Try to make a Class (e.g Room) and store Room class object in a array. Search and show from that array. No need to maintain to array for this.
enter image description hereI am having trouble implementing a didSelectRowAtIndexPath from a searchBar filtered tableView cell. When I update the tableView list based on searchbar textDidChange, and perform a show segue to another ViewController, the navigationBar Title is always displaying the non-filtered tableViews indexPath 0. In other words, I would like to have the navigation title display the text from the didSelectAtIndex of search results tableView (not the original indexPath cell text from the non-filtered tableView). Hopefully that makes sense, and thanks in advance!
// viewDidLoad method
override func viewDidLoad() {
super.viewDidLoad()
searchBar.searchBarStyle = UISearchBarStyle.prominent
searchBar.placeholder = " Search Places..."
searchBar.sizeToFit()
searchBar.isTranslucent = false
searchBar.backgroundImage = UIImage()
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
navigationItem.titleView = searchBar
ref = FIRDatabase.database().reference()
fetchPlaces()
placesClient = GMSPlacesClient.shared()
locationManager.requestAlwaysAuthorization()
tableView.allowsMultipleSelectionDuringEditing = true
}
var placeList = [Place]()
var placesDictionary = [String: Place]()
// fetch places for tableView method
func fetchPlaces() {
let uid = FIRAuth.auth()?.currentUser?.uid
let ref = FIRDatabase.database().reference().child("users").child(uid!).child("Places")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let place = Place()
place.setValuesForKeys(dictionary)
if let addedBy = place.addedBy {
self.placesDictionary[addedBy] = place
self.placeList.insert(place, at: 0)
}
//this will crash because of background thread, so lets call this on dispatch_async main thread
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
// search variables
lazy var searchBar:UISearchBar = UISearchBar()
var isSearching = false
var filteredData = [Place]()
// searchBar method
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
isSearching = false
view.endEditing(true)
tableView.reloadData()
} else {
isSearching = true
filteredData = placeList.filter({$0.place?.range(of: searchBar.text!) != nil})
tableView.reloadData()
}
}
// tableView methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching {
return filteredData.count
}
return placeList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId)
if isSearching {
cell.textLabel?.text = filteredData[indexPath.row].place
} else {
cell.textLabel?.text = placeList[indexPath.row].place
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let placeDetailsVC = CurrentUserPlaceDetailsVC()
if isSearching == false {
placeDetailsVC.navigationTitle = placeList[(indexPath.row)].place
show(placeDetailsVC, sender: self)
} else {
placeDetailsVC.navigationTitle = filteredData[(indexPath.row)].place
show(placeDetailsVC, sender: self)
}
}
}
Create a string in your up coming ViewController.
class CurrentUserPlaceDetailsVC: UIViewController {
var navigationTitle: String?
override func viewDidLoad(){
super.viewDidLoad()
self.navigationItem.title = navigationTitle
}
}
Now instead of assigning the title directly to navigationBar you should assign it first to that string and then to navigationBar in viewDidLoad method of your viewController.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let placeDetailsVC = CurrentUserPlaceDetailsVC()
// Get Cell Label
placeDetailsVC.navigationTitle = placeList[indexPath.row].place
show(placeDetailsVC, sender: self)
}