Kch
Kch

Reputation: 69

How to populate a picker view with a dictionary?

I want to populate a picker with a dictionary and have trouble with the title for row.

Basically my picker should have 2 components and be as follows

|Alabama| ---> ["35010", "35011","35012"]
|Arizona| ---> ["99501", "99502"]
|California|--> ["90001", "90002", "90003"]

The first picker should have the states and based on the state the second picker should have different values. I have set the number of components as two as created a dictionary with the state as keys and codes as the component values, but I am unable to populate the values in the picker.

 class dependentPicker: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {



     @IBOutlet weak var dependentPicker: UIPickerView!

        let stateToPostalCodeDB = [
            "Alabama" :[ "35010","35011","35012"]
            "Arizona" : [ "85001", "85002"],
            "California": [ "90001","90002","90003"]]

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return 2
        }
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            //
            }
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {

            //

        }

I can make the solution with two pickers but I want to know how to do the same with 2 components and dictionaries. I basically want to learn how to populate the picker with dictionaries.

Upvotes: 1

Views: 552

Answers (2)

Daniel
Daniel

Reputation: 3597

This answer is quite similar to CodeDifferent's great answer, but with two main differences:

  1. An array of State structs replaces the dictionary.
  2. Hardcoded values replace the PickerViewComponents enum. I think the enum adds unnecessary wordiness and complexity.

Here is the main view controller class:

class ViewController: UIViewController {
    @IBOutlet weak var pickerView: UIPickerView!

    let states = [
        State(name: "Alabama", postalCodes: ["35010", "35011","35012"]),
        State(name: "Arizona", postalCodes: ["99501", "99502"]),
        State(name: "California", postalCodes: ["90001", "90002", "90003"])
    ]

    override func viewDidLoad() {
        pickerView.dataSource = self
        pickerView.delegate = self
    }
}

Data source and delegate:

extension ViewController: UIPickerViewDataSource, UIPickerViewDelegate {
    /// Number of columns
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 2
    }

    /// Number of rows per column
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        if component == Component.state {
            return states.count
        } else {
            let index = pickerView.selectedRow(inComponent: Component.state)
            return states[index].count
        }
    }

    /// Title for row/column
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        if component == Component.state {
            return states[row]
        } else {
            let index = pickerView.selectedRow(inComponent: Component.state)
            return states[index].postalCodes[row]
        }
    }

    /// Update zip codes if state changes
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        if component == Component.state {
            pickerView.reloadComponent(Component.postalCode)
            pickerView.selectRow(0, inComponent: Component.postalCode, animated: true)
        }
    }
}

Structs:

struct State {
    var name: String
    var postalCodes: [String]
}

struct Component {
    static let state = 0
    static let postalCode = 1
}

Upvotes: 1

Code Different
Code Different

Reputation: 93141

You are better off creating a structure to hold the states and their zip codes. Dictionary will get confusing real quick. Anyhow, here's the code. See the comment within.

class ViewController: UIViewController {
    @IBOutlet weak var pickerView: UIPickerView!
    var stateToPostalCodeDB = [
        "Alabama": ["35010", "35011","35012"],
        "Arizona": ["99501", "99502"],
        "California": ["90001", "90002", "90003"]
    ]

    /// The keys in a dictionary are unordered
    /// Create a computed property that sorts the states alphabetically
    var states: [String] { return stateToPostalCodeDB.keys.sorted() }

    override func viewDidLoad() {
        super.viewDidLoad()
        pickerView.dataSource = self
        pickerView.delegate = self
    }
}

/// An enum to define the columns of the UIPickerView
/// Better than using 0 or 1 or any mysterious number
private enum PickerViewComponents: Int, CaseIterable {
    case state, zipCode
}

/// The data source and delegate of the Picker View
extension ViewController: UIPickerViewDataSource, UIPickerViewDelegate {
    /// The number of columns of the Picker View
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return PickerViewComponents.allCases.count
    }

    /// The number of rows for each columns
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        let componentName = PickerViewComponents.allCases[component]
        switch componentName {
        case .state:
            return states.count
        case .zipCode:
            let index = pickerView.selectedRow(inComponent: PickerViewComponents.state.rawValue)
            let state = states[index]
            return stateToPostalCodeDB[state]!.count
        }
    }

    /// The label for each row
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        let componentName = PickerViewComponents.allCases[component]
        switch componentName {
        case .state:
            return states[row]
        case .zipCode:
            let index = pickerView.selectedRow(inComponent: PickerViewComponents.state.rawValue)
            let state = states[index]
            return stateToPostalCodeDB[state]?[row]
        }
    }

    /// If the user changes the state, reload the zip codes
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        let componentName = PickerViewComponents.allCases[component]
        if componentName == .state {
            let zipCode = PickerViewComponents.zipCode.rawValue
            pickerView.reloadComponent(zipCode)
            pickerView.selectRow(0, inComponent: zipCode, animated: true)
        }
    }
}

Upvotes: 1

Related Questions