Pulling Data From Firebase Issue

后端 未结 3 1144
孤街浪徒
孤街浪徒 2021-01-25 00:51

Novice programmer, currently working on an ios app using firebase as our backend. I\'m trying to grab values from the firebase database to populate a TableView, but there are tw

相关标签:
3条回答
  • 2021-01-25 01:25

    Firebase is asynchronous: it takes time for values to be returned from Firebase via a query or observe call.

    Your code isn't allowing for that so whats happening is the observeSingleEvent is called and the cellForRowAt function is returning (an empty) cell before Firebase has a chance to return the data and populate committee.

    Code is way faster than the internet so you need to operate on data within the observe blocks (closures) as that's the time when it's valid.

    Best practice is to populate the datasource within an Firebase observe block (closure) - here are the steps

    1. Get the values from Firebase via observe .value block
    2. Within the block, iterate over the returned values and populate an array (datasource)
    3. Reload the tableView

    and to sort by Head Char, here's a swifty solution. Assume the dict is populated with .values from Firebase

    let dict = ["0": ["name": "First Disarmament and International Security Committee",
                  "Head Chair": "Jessie Mao"],
                "1": ["name": "UN Special,  Political And Decolonization Committee",
                  "Head Chair": "Trevor Dowds"],
                "2": ["name": "UN 6th Legal Committee",
                  "Head Chair": "Benjy Malings"]
    ]
    let sortedArray = Array(dict).sorted { $0.1["Head Chair"]! < $1.1["Head Chair"]! }
    
    0 讨论(0)
  • 2021-01-25 01:39

    Can you try to change this:

    let sectionRef = FIRDatabase.database().reference().child(String(indexPath.section))
    

    to this:

    let sectionRef = FIRDatabase.database().reference(withPath: "Committee").child(String(indexPath.section))
    
    0 讨论(0)
  • 2021-01-25 01:42

    In general: It's best practise to create a function for your download purposes and call the function instead of putting all the lines of code inside your cell declaration:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "committeeCell", for: indexPath) as! CommitteeTableViewCell
    
        // we are passing the Label and the indexPath to out func
        myFirebaseFunc(label: cell.committeeLabel, indexPath: indexPath)
        return cell
    }
    

    Also you should use observeEventType, to handle the case, that there might be data added to your Firebase during runtime and you want the data to be added to your UITableView.

    func myFirebaseFunc(label: UILabel, indexPath: NSIndexPath) {
    
        let sectionRef = FIRDatabase.database().reference(withPath: "Committee".child(String(indexPath.section))
        let committeeRef = sectionRef.child(String(indexPath.row))
        let currentRef = committeeRef.child(String(indexPath.row)).child("name")
        currentRef.observeEventType(of: .value, withBlock: { (snapshot) in
    
            if snapshot.exists() {
                label?.text = snapshot.value as? String
            }
        })
    }
    

    To your case: Also I assume that your App crashes because indexPath out of bounds. It's not best practise to download your Firebase data according to your indexPath within the population of the Cell. Since, as stated correctly by Jay, the download is async.

    You have to download your desired data first and then populate a tableView according to your data like this:

    You create an Array of Models (struct) or NSMutableDictionary.

    // first you check, if your snapshot exists
    if snapshot.exists() {
    
        // you delete your Array, you your data will not be added multiple
        // times once you add data in the background and the func gets
        // called again
    
        self.myArray.removeAll()
    
        // you sort your snapshot according to your needs (for example of the (pre set) date value. If date is missing, the code will not crash!
        let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])
    
        // then you loop through your sorted Array
        for element in sorted {
    
           // you create a name String of your name element in your Dictionary
           let name = element.valueForKey("name")! as? String
    
           // if you have created a struct, you instantiate a model and set the name
           let m = MyStruct(name: name!)
    
           // and you append it to your Array     
           self.myArray.append(m)      
        }
        // last but not least we reload our tableView on the main threat
        DispatchQueue.main.async{
            self.tableView.reloadData()
        }
    }
    

    Then you set numbersOfRowsInSection to myArray.count

    And fill your tableView with:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "committeeCell", for: indexPath) as! CommitteeTableViewCell
    
        let mine = myArray[indexPath.row]
        cell.committeeLabel.text = mine.name
        return cell
    }
    

    This would be our struct in this example:

    struct MyStuct {
        var name: String = ""
    }
    

    Our Array instantiated in the ViewController class:

    let myArray: [MyStruct] = []
    

    In this best practise scenario we would call our myFirebaseFunc func at the viewDidAppear func of our ViewController.

    0 讨论(0)
提交回复
热议问题