Search code examples
iosswiftparse-platformuisearchbarpfuser

Smart-search for Parse Usernames in Swift not working


I'm trying to have a smart-search in my iOS app so that when a user types a character into a UISearchBar, the results automatically update in the tableview below the searchbar. For some reason, the searchBar function with textDidChange isn't called when I type a character into the search bar. It's called after 2 characters are typed. So my search results are always 1 step behind what's actually typed into the search bar. And it also seems like the search() function is being called twice every time. Any ideas?

//FUNC: search
func search(searchText: String? = nil){

        if searchText == nil || searchText == "" {
            println("No users found.")

        } else {

            var query = PFUser.query()

            //MAKE CASE INSENSTIVE
            query!.whereKey("username", containsString: searchText!)

            query!.findObjectsInBackgroundWithBlock { (results, error) -> Void in

                if error != nil {
                    println(error)
                } else {
                    if let res = results {
                        self.data = res as? [PFUser]
                    }
                }
            }
        }
        self.tableView.reloadData()

}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
    let user = data[indexPath.row] as! PFUser
    let username = user.username
    println(username)
    cell.textLabel?.text = username
    return cell

}


func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
    search(searchText: searchText)
    searchActive = false;

}

Solution

  • You will want to implement the UISearchResultsUpdating protocol to achieve this. It uses a UISearchController (introduced in iOS 8) which has to be added programmatically instead of through the storyboard, but don't worry, it's pretty straight-forward.

    This should get the job done for you

    Cheers, Russell

    class YourTableViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
        var searchUsers: [PFUser] = [PFUser]()
        var userSearchController = UISearchController()
        var searchActive: Bool = false
    
        // MARK: - Lifecycle
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.userSearchController = UISearchController(searchResultsController: nil)
            self.userSearchController.dimsBackgroundDuringPresentation = true
    
            // This is used for dynamic search results updating while the user types
            // Requires UISearchResultsUpdating delegate
            self.userSearchController.searchResultsUpdater = self
    
            // Configure the search controller's search bar
            self.userSearchController.searchBar.placeholder = "Search for a user"
            self.userSearchController.searchBar.sizeToFit()
            self.userSearchController.searchBar.delegate = self
            self.definesPresentationContext = true
    
            // Set the search controller to the header of the table
            self.tableView.tableHeaderView = self.userSearchController.searchBar
        }
    
        // MARK: - Parse Backend methods
    
        func loadSearchUsers(searchString: String) {
            var query = PFUser.query()
    
            // Filter by search string
            query.whereKey("username", containsString: searchString)
    
            self.searchActive = true
            query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
    
                if (error == nil) {
                    self.searchUsers.removeAll(keepCapacity: false)
                    self.searchUsers += objects as! [PFUser]
                    self.tableView.reloadData()
                } else {
                    // Log details of the failure
                    println("search query error: \(error) \(error!.userInfo!)")
                }
                self.searchActive = false
            }
        }
    
        // MARK: - Search Bar Delegate Methods
    
        func searchBarSearchButtonClicked(searchBar: UISearchBar) {
    
            // Force search if user pushes button
            let searchString: String = searchBar.text.lowercaseString
            if (searchString != "") {
                loadSearchUsers(searchString)
            }
        }
    
        func searchBarCancelButtonClicked(searchBar: UISearchBar) {
    
            // Clear any search criteria
            searchBar.text = ""
    
            // Force reload of table data from normal data source
        }
    
        // MARK: - UISearchResultsUpdating Methods
    
        // This function is used along with UISearchResultsUpdating for dynamic search results processing
        // Called anytime the search bar text is changed
        func updateSearchResultsForSearchController(searchController: UISearchController) {
    
            let searchString: String = searchController.searchBar.text.lowercaseString
            if (searchString != "" && !self.searchActive) {
                loadSearchUsers(searchString)
            }
        }
    
        // MARK: - Table view data source
    
        override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
            return 1
        }
    
        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            if (self.userSearchController.active) {
                return self.searchUsers.count
            } else {
                // return whatever your normal data source is
            }
        }
    
        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            var cell = tableView.dequeueReusableCellWithIdentifier("userCell") as! UserCell
    
            if (self.userSearchController.active && self.searchUsers.count > indexPath.row) {
                // bind data to the search results cell
            } else {
                // bind data from your normal data source
            }
    
            return cell
        }
    
        // MARK: - UITableViewDelegate
    
        override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
            tableView.deselectRowAtIndexPath(indexPath, animated: true)
    
            if (self.userSearchController.active && self.searchUsers.count > 0) {
                // Segue or whatever you want
            } else {
                // normal data source selection
            }
        }
    }