简体   繁体   中英

How to place nodes of Firebase Data in Array

On the first page of my app, I am using a UIViewPicker which allows the user to select which country they want to choose. Instead of me hardcoding country names into the UIPicker via array (shown below):

var country = ["USA", "CANADA"]

I decided it would be best if the app would reach out to my firebase database where it could get all the countries, and append them to the array instead, and from which the UIPicker would read off from.

That way if I ever wanted to add more countries, I wouldn't have to release a new version of the app. I could simply just add another country within the database, and the app would automatically update it. But I am not quite sure on how to do this. When I print the snapshot of the database, I get the entire database, but I would just like to get USA and Canada into an array.

So here is the structure of my firebase database:

countries
  Canada
     states
        Alberta
        British Columbia
  USA
     states
        Alabama
        Arkansas

Here is my current code:

import UIKit
import FirebaseDatabase

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

    @IBOutlet weak var countryPicker: UIPickerView!

    //let country = ["USA", "Canada"] I am no longer using this hardcoded array

    var countrySelected = String()
    var action = 0 

    override func viewDidLoad() {
        super.viewDidLoad()

        //this is where I reach out to the database
        let database = Database.database().reference()
        database.child("countries").observeSingleEvent(of: .value) { (Snapshot) in
            print(Snapshot)
        }

        //add code here that takes the snapshot and appends the the array below
        var country = string()

        countrySelected = country[0]
        countryPicker.selectRow(action, inComponent: 0, animated: false)
        //print(countrySelected)
    }

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return country.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return country[row]
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        countrySelected = country[row]
        action = country.firstIndex(of: countrySelected)!
        //print(countrySelected)
    }
}

If someone could help explain, that would be awesome!

To make things easier, we can create some structs to represent the countries and states.

struct Country {
    struct State {
        let country: String
        let name: String
    }

    let name: String
    let states: [State]

    static var ref: DatabaseReference {
        return Database.database().reference(withPath: "countries")
    }

    static func from(key: String, value: Any?) -> Country? {
        guard
            let value = value as? [String:[String]],
            let states = value["states"] else { return nil }

        return Country(name: key, states: states.map {
            State(country: key, name: $0)
        })
    }

    static func countries(from snapshot: DataSnapshot) -> [Country]? {
        return snapshot.snapshots.compactMap {
            Country.from(key: $0.key, value: $0.value)
        }
    }
}

And an extension to DataSnapshot to give us a convenient accessor to the children of a snapshot.

extension DataSnapshot {
    var snapshots: [DataSnapshot] {
        return children.allObjects as? [DataSnapshot] ?? []
    }
}

Next we create some properties to handle the countries – an array to store them and some computed properties to return the selected country and state.

class CountryStatePickerViewController: UIViewController {

    // MARK: Outlets

    @IBOutlet weak var pickerView: UIPickerView! {
        didSet {
            loadCountries()
        }
    }

    // MARK: Properties

    private var countries = [Country]() {
        didSet {
            pickerView.reloadAllComponents()
        }
    }

    var selectedCountry: Country? {
        return country(at: pickerView.selectedRow(inComponent: 0))
    }

    var selectedState: Country.State? {
        return selectedCountry.flatMap {
            state(at: pickerView.selectedRow(inComponent: 1), in: $0)
        }
    }
}

We also need a few methods to safely access countries and states at a specific index, as well as one for loading the countries in the first place.

extension DataSnapshot {
    var snapshots: [DataSnapshot] {
        return children.allObjects as? [DataSnapshot] ?? []
    }
}

extension CountryStatePickerViewController {

    private func loadCountries() {
        Country.ref.observeSingleEvent(of: .value, with: { [weak self] in
            self?.countries = Country.countries(from: $0) ?? []
        })
    }

    private func country(at index: Int) -> Country? {
        return countries.indices.contains(index) ? countries[index] : nil
    }

    private func state(at index: Int, in country: Country) -> Country.State? {
        return country.states.indices.contains(index) ? country.states[index] : nil
    }
}

Lastly in our UIPickerView protocol conformances, the countries array fills the datasource. We can use two components to represent the countries and their corresponding states.

extension CountryStatePickerViewController: UIPickerViewDataSource, UIPickerViewDelegate {

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 2
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        switch component {
        case 0:
            return countries.count
        case 1:
            return selectedCountry?.states.count ?? 0
        default:
            return 0
        }
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        switch component {
        case 0:
            return country(at: row)?.name
        case 1:
            return selectedCountry.flatMap { state(at: row, in: $0)?.name }
        default:
            return nil
        }
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

        if component == 0 { pickerView.reloadComponent(1) }

        selectedState.flatMap { print($0.name, $0.country) }
    }
}
var countries : [Any] = []

override func viewDidLoad() {
    super.viewDidLoad()
    database.child("countries").observeSingleEvent(of: .value) { (snapshot) in
        if snapshot.exists() {
            if let dict : [String : AnyObject] = snapshot.value as? [String : AnyObject] {
                let array : [Any] = (dict as AnyObject).allValues
                self.countries = array
                // reload countryPicker data 

            } 
        } 
        else {
            // not exist in db 
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM