tccpg288 tccpg288 - 1 year ago 56
Swift Question

Firebase Data Returning Nil

I am trying to create programmatic

Radio Buttons
based on dynamic Firebase data. The number of
Radio Buttons
is dependent on an integer value stored in Firebase, named
numberOfChildren
.

The value I am receiving from Firebase is coming back
nil
and I cannot figure out why. Any help on how to resolve this issue so that I can return an integer value would be appreciated:

import UIKit
import FirebaseDatabase
import DLRadioButton


class PollController: UIViewController {

@IBOutlet weak var passLabel: UILabel!
@IBOutlet weak var pollImage: UIImageView!

var ref: FIRDatabaseReference!
var pollRef: FIRDatabaseReference!

var pass = ""
var passedImageURL = ""

var posX = 0;
var posY = 0;

var numberOfChildren: Int!

let label2 = UILabel(frame: CGRect(x: 90, y: 160, width: 200, height: 70))

override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
pollRef = ref.child("Polls").child(pass)
passLabel.text = pass
pollImage.sd_setImage(with: URL(string: passedImageURL), placeholderImage: UIImage(named: "test"))

pollRef.observe(FIRDataEventType.value, with: {(snapshot) in
self.numberOfChildren = Int(snapshot.childSnapshot(forPath: "answers").childrenCount)
self.passLabel.text = String(self.numberOfChildren)
print(self.numberOfChildren)
})

var buttons = [DLRadioButton]()

for x in 0..<self.numberOfChildren {
let firstRadioButton = self.createRadioButton(frame: CGRect(x: CGFloat(x)*32, y: self.view.center.y, width: 40.0, height: 20.0), title: String(x), color: UIColor.green)
firstRadioButton.tag = x
buttons.append(firstRadioButton)
self.view.addSubview(firstRadioButton);
}

let groupButtons = DLRadioButton()
groupButtons.otherButtons = buttons

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

private func createRadioButton(frame : CGRect, title : String, color : UIColor) -> DLRadioButton {
let radioButton = DLRadioButton(frame: frame);
radioButton.titleLabel!.font = UIFont.systemFont(ofSize: 14);
radioButton.setTitle(title, for: UIControlState.normal);
radioButton.setTitleColor(color, for: UIControlState.normal);
radioButton.iconColor = color;
radioButton.indicatorColor = color;
radioButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
radioButton.addTarget(self, action: #selector(self.logSelectedButton(_:)), for: UIControlEvents.touchUpInside);
return radioButton;
}

@objc private func logSelectedButton(_ sender: DLRadioButton){

print("Selected Button Tag = \(sender.tag) and Title \(sender.titleLabel?.text)")
}

}

Answer Source

The problem is in the way you nest the code. Firebase loads the data asynchronously, that's why you pass in a callback block: so that it can call your code block once the data has loaded. By the time you look over self.numChildren that data hasn't loaded yet.

The solution is to move the code that requires numChildren into the callback block.

pollRef.observe(FIRDataEventType.value, with: {(snapshot) in
    self.numberOfChildren = Int(snapshot.childSnapshot(forPath: "answers").childrenCount)
    self.passLabel.text = String(self.numberOfChildren)

    var buttons = [DLRadioButton]()

    for x in 0..<self.numberOfChildren {
        let firstRadioButton = self.createRadioButton(frame: CGRect(x: CGFloat(x)*32, y: self.view.center.y, width: 40.0, height: 20.0), title: String(x), color: UIColor.green)
        firstRadioButton.tag = x
        buttons.append(firstRadioButton)
        self.view.addSubview(firstRadioButton);
    }

    let groupButtons = DLRadioButton()
    groupButtons.otherButtons = buttons
})

This way the loop is only invoked once numChildren has been initialized from the database.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download