Reputation: 69
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
Reputation: 3597
This answer is quite similar to CodeDifferent's great answer, but with two main differences:
State
structs replaces the dictionary.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
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