How can I use queryStartingAtValue in my searchcontroller to search for users?

谁说胖子不能爱 提交于 2019-12-18 09:48:43

问题


I have previously asked a question about searching for users the most cost-efficient way (without having to load up every user in the entire database.

My code before the question was

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()
}

func loadProfileData() {
    databaseRef.child("profile").queryOrdered(byChild: "username").observe(.childAdded, with: { (snapshot) in
        print(snapshot)
        let userObj =  Mapper<UserModel>().map(JSONObject: snapshot.value!)
        userObj?.uid = snapshot.key

        guard snapshot.key != self.loggedInUser?.uid else { return }

        self.usersArray.append(userObj!)
    })
}

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!)
    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()))
        }
    }
   }
  }

But then user "maxwell" replied to me and suggested to use queryStartingAtValue like this:

func searchQueryUsers(text: String, completion: @escaping (_ userNames: [String]) -> Void) {

    var userNames: [String] = []

    databaseRef.child("profile").queryOrdered(byChild: "username").queryStarting(atValue: text).observeSingleEvent(of: .value, with: { snapshot in

        for item in snapshot.children {

            guard let item = item as? DataSnapshot else {
                break
            }

            //"name" is a key for name in FirebaseDatabese model
            if let dict = item.value as? [String: Any], let name = dict["name"] as? String {
                userNames.append(name)
            }
        }

        completion(userNames)
    })
}

How can I implement this, with my already existing code with my Mapper<UserModel>().map(JSONObject)? I tried implementing his code and couldn't really find a way to do this effectively, and I can't seem to get to maxwell, so can anyone help me out with this?

Should i use searchController.searchBar.text for the "text" in maxwells code?

Thanks,

Updated for Jay:

 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 > 0 {
        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]
}

func searchBar(_ searchBar: UISearchBar,
               textDidChange searchText: String) {

    self.searchUsers(text: searchText)
}




 }

回答1:


The code in the question is almost correct, just need to tweak it to ensure the enumeration var is a snapshot and then use it with Mapper.

As a user enters a user name, recursively call this with each keypress; so if they are typing in Leroy, L is typed first and this code will retrieve all nodes where the username value starts with "L". Then the user types 'e' making it 'Le' etc etc.

func searchUsers(text: String) {
    if text.count > 0 {
       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

EDIT:

OP requested code for handling the search through a searchBar and a if statement to prevent searching if there are is no text. Here's the delegate method for that which calls the searchUsers function above

func searchBar(_ searchBar: UISearchBar,
               textDidChange searchText: String) {

    self.searchUsers(text: searchText)
}

EDIT:

OP wanted to see my viewDidLoad function, as unexciting as it is, so here it is.

class ViewController: UIViewController, UISearchBarDelegate 
    override func viewDidLoad() {
       super.viewDidLoad()

       // Do any additional setup after loading the view.
    }



回答2:


I am adding this as a separate answer as the OP has additional indirectly related questions:

This is my entire codebase

class ViewController: UIViewController, UISearchBarDelegate {
    @IBOutlet var searchBarOutlet: [UISearchBar]!
     override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.
        self.searchBar.delegate = self
     }

    var userNamesArray = [String]()
    func searchBar(_ searchBar: UISearchBar,
                   textDidChange searchText: String) {

        self.searchUsers(text: searchText)
    }

    func searchUsers(text: String) {
        self.userNamesArray = []
        if text.count > 0 {
            let ending = text + "\u{f8ff}"
            let databaseRef = self.ref.child("users")
            databaseRef.queryOrdered(byChild: "Name")
                .queryStarting(atValue: text)
                .queryEnding(atValue: ending)
                .observeSingleEvent(of: .value, with: { snapshot in
                    for child in snapshot.children {
                        let childSnap = child as! DataSnapshot
                        let userName = childSnap.childSnapshot(forPath: "Name").value as! String
                        self.userNamesArray.append(userName)
                    }
                    print(self.userNamesArray) //here you would call tableView.reloadData()
                })
        }
    }
}

That's it other than assigning self.ref to my Firebase. My structure is:

users
  uid_0
    name: "Frank"
  uid_1
    name: "Fred"
  uid_2
    name: "Finay"

etc when I type in the search field 'F' I get the following output

["Frank", "Fred", "Friday"]

then when I type 'Fr', I get the following output

["Frank", "Fred"]

So as you can see, it works. If it's not working in your case, you may not have your tableView connected correctly or some other issue unrelated to the search.

Now, I am not using a tableView but simply printing name strings to the console so you would need to do a tableView.reloadData() in the place of my print statement.



来源:https://stackoverflow.com/questions/52974684/how-can-i-use-querystartingatvalue-in-my-searchcontroller-to-search-for-users

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!