SearchBar problem while trying to search Firestore

2019-08-20 23:16发布

问题:

I have a tableView and I use infinite scroll to populate firestore data with batches. Also I have a searched bar and I am trying to query firestore with the text from the text bar and then populate it in the tableview. I have 3 main problems.

  1. When I click search thee first time I get an empty array and an empty tableview, but when I click search the second time everything seems fine.

  2. When I finally populate the searched content I want to stop fetching new content while I am scrolling.

  3. If I text a wrong word and press search then I get the previous search and then the "No Ingredients found" printed twice.

This is my code for searchBar:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let text = searchBar.text else {return}
        searchIngredients(text: text)
        self.searchBarIngredient.endEditing(true)
        print("\(searchIngredients(text: text))")
    }

The code for function when I click search

func searchIngredients(text: String) -> Array<Any>{

        let db = Firestore.firestore()

        db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
            if let err = err {
                print("\(err.localizedDescription)")
                print("Test Error")
            } else {
                if (querySnapshot!.isEmpty == false){
                    self.searchedIngredientsArray = querySnapshot!.documents.compactMap({Ingredients(dictionary: $0.data())})

                }else{
                    print("No Ingredients found")
                }
            }
        }

         self.tableView.reloadData()
         ingredientsArray = searchedIngredientsArray

        return ingredientsArray

    }

Finally the code for scrolling

func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let off = scrollView.contentOffset.y
        let off1 = scrollView.contentSize.height

        if off > off1 - scrollView.frame.height * leadingScreensForBatching{
            if !fetchMoreIngredients && !reachEnd{
                beginBatchFetch()
            }
        }
    }

I don't write the beginBatchFetch() cause its working fine and I don't think is relevant. Thanks in advance.

回答1:

The issue in your question is that Firestore is asynchronous.

It takes time for Firestore to return documents you've requested and that data will only be valid within the closure calling the function. The code outside the closure will execute way before the data is available within the closure.

So here's what's going on.

func searchIngredients(text: String) -> Array<Any>{
    let db = Firestore.firestore()
    db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
        //the data has returned from firebase and is valid
    }
    //the code below here will execute *before* the code in the above closure
    self.tableView.reloadData()
    ingredientsArray = searchedIngredientsArray
    return ingredientsArray
}

what's happening is the tableView is being refreshed before there's any data in the array.

You're also returning the ingredientsArray before it's populated. More importantly, attempting to return a value from an asynchronous function can (and should) generally be avoided.

The fix is to handle the data within the closure

class ViewController: NSViewController {
   var ingredientArray = [String]()
   func searchIngredients(text: String) {
         let db = Firestore.firestore()
         db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
             //the data has returned from firebase and is valid
             //populate the class var array with data from firebase
             //    self.ingredientArray.append(some string)
             //refresh the tableview
         }
     }

Note that the searchIngredients function should not return a value - nor does it need to