I'm working on an app where I need to store a Country and City in a Firebase database. Besides storing, I also need to retrieve that info and present to the user in a pickerView. Given that, I need to read the Country and City from the Database, check what is their index and set it in pickerView.
Countries and Cities are store in JSON
{
"Country": [
{
"name": "UK",
"cities": [
{
"name": "London"
},
{
"name": "Manchester"
},
{
"name": "Bristol"
}
]
},
{
"name": "USA",
"cities": [
{
"name": "New York"
},
{
"name": "Chicago"
}
]
},
{
"name": "China",
"cities": [
{
"name": "Beijing"
},
{
"name": "Shanghai"
},
{
"name": "Shenzhen"
},
{
"name": "Hong Kong"
}
]
}
]
}
My code to read JSON is
// Declared in Class
var countryList = [NSDictionary]()
var selectedRow = [NSDictionary]()
var selectedCity = ""
var selectedCountry = ""
func readJson() {
if let path = Bundle.main.path(forResource: "Countries", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject>, let country = jsonResult["Country"] as? [NSDictionary] {
//handles the array of countries on your json file.
self.countryList = country
self.selectedRow = self.countryList.first?.object(forKey: "cities") as! [NSDictionary]
}
} catch {
print("error loading countries")
// handle error
}
}
}
The code above allows me to feed a UIPickerView with 2 sessions and the Country and the list of cities within that Country. From there, I can also identify which Country and City were selected.
AS part of my code I have a func that would allow me to identify what is indexes of the saved Country(countryIndex) and City(cityIndex) in UIPickerView so that I can set it and that's where my issues start
func indexOf(city: String, inCountry country: String, in countries: [Country]) -> (countryIndex: Int, cityIndex: Int)? {
// countries is an array of [Country]
// var countries = [Country]()
guard let countryIndex = countries.firstIndex(where: {$0.name == country}), let cityIndex = countries[countryIndex].cities.firstIndex(where: {$0.name == city}) else {return nil}
//let cityIndex = 0
return (countryIndex, cityIndex)
} // courtesy of @flanker
This func was working perfectly fine when my Countries and Cities were stored to a [Country] but is not working with NSDictionary coming from JSON.
I have tried to 1) Change [Country] by [NSDictionary], "countries" by "countryList" and "name" by "Country" Here I receive and error "NSDictionary has no member Country" I also tried to leave just $0 == Country which hasn't worked as well. 2) Tried also "countryList.firstIndex(of: "USA")" but got the error below Cannot convert value of type 'String' to expected argument type 'NSDictionary'
Anyone would be able to assist? How can I make the func indexOf work again?
Thanks
Updated according to @vadian's suggestion
My updated code is
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var pickerView: UIPickerView!
@IBOutlet weak var countryLbl: UILabel!
var familyNames: [String] = []
var fontName = "Arial"
let fontCount = 0
var countryList = [Country]()
var selectedRow = [City]()
var selectedCity : City?
var selectedCountry : Country?
struct Root : Decodable {
let country : [Country] // better plural let countries
private enum CodingKeys : String, CodingKey { case country = "Country" }
}
struct Country : Decodable {
var name : String
var cities : [City]
}
struct City : Decodable {
var name : String
}
override func viewDidLoad() {
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
fontName = "HelveticaNeue"
}
func indexOf(city: String, inCountry country: String, in countries: [Country]) -> (countryIndex: Int, cityIndex: Int)? {
guard let countryIndex = countries.firstIndex(where: {$0.name == country}), let cityIndex = countries[countryIndex].cities.firstIndex(where: {$0.name == city}) else {return nil}
return (countryIndex, cityIndex)
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
if component == 0 {
return 80
} else {
return 300
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return countryList.count
} else {
return selectedRow.count
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var rowTitle = ""
let pickerLabel = UILabel()
pickerLabel.textColor = UIColor.blue
switch component {
case 0:
rowTitle = countryList[row].name
case 1:
rowTitle = selectedRow[row].name
default:
break
}
pickerLabel.text = rowTitle
pickerLabel.font = UIFont(name: fontName, size: 20.0)
pickerLabel.textAlignment = .center
return pickerLabel
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.reloadAllComponents()
if component == 0 {
self.selectedCountry = self.countryList[row]
self.selectedRow = self.countryList[row].cities
pickerView.reloadComponent(1)
self.pickerView.selectRow(0, inComponent: 1, animated: true)
self.selectedCity = self.selectedRow[0]
} else {
self.selectedCity = self.selectedRow[row]
}
if let indexes = indexOf(city: self.selectedCity!.name, inCountry: self.selectedCountry!.name, in: countryList) {
//do something with indexes.countryIndex and indexes.cityIndex
print("This is the result \(indexes.cityIndex) and \(indexes.countryIndex)")
}
countryLbl.text = "The right answer is: \(self.selectedCountry?.name) and the city is \(self.selectedCity?.name)"
}
func readJson() {
let url = Bundle.main.url(forResource: "Countries", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(Root.self, from: data)
//handles the array of countries on your json file.
self.countryList = jsonResult.country
self.selectedRow = self.countryList.first!.cities
} catch {
print("error loading countries", error)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
readJson()
}
}