Here's a solution. Not the most elegant, but it works.
JSON structure:
[
{
"brand": "BMW",
"models": [
{
"name": "335"
},
{
"name": "x5"
}
]
},
{
"brand": "Tesla",
"models": [
{
"name": "CyberTruck"
},
{
"name": "X"
},
{
"name": "S"
}
]
},
{
"brand": "Porsche",
"models": [
{
"name": "911"
},
{
"name": "Taycan"
}
]
}
]
Models:
struct Manufacturer: Decodable {
let brand: String
let models: [Model]
}
struct Model: Decodable {
let name: String
}
And the actual view and its model:
struct ContentView: View {
@ObservedObject private var model = ContentViewModel()
var body: some View {
VStack {
if model.selectedManufacturer == -1 {
Text("No Brand Selected")
} else {
Text("\(model.carData[model.selectedManufacturer].brand)")
}
Picker(selection: $model.selectedManufacturer, label: Text("Brand")) {
Text("None")
.tag(-1)
ForEach(0 ..< model.carData.count) { carIndex in
Text(self.model.carData[carIndex].brand)
.tag(carIndex)
}
}
if model.selectedManufacturer != -1 {
Picker(selection: $model.selectedModel, label: Text("Model")) {
Text("None")
.tag(-1)
ForEach(0 ..< model.models.count, id: \.self) { modelIndex in
Text(self.model.models[modelIndex].name)
.tag(modelIndex)
}
}
}
}
}
}
class ContentViewModel: ObservableObject {
let carData: [Manufacturer]
@Published var selectedManufacturer = -1 {
didSet {
// reset the currently selected model to "None" when the manufacturer changes
selectedModel = -1
}
}
@Published var selectedModel = -1
var models: [Model] {
if (0 ..< carData.count).contains(selectedManufacturer) {
return carData[selectedManufacturer].models
}
return []
}
init() {
let url = Bundle.main.url(forResource: "TestCars", withExtension: "json")!
let data = try! Data(contentsOf: url)
carData = try! JSONDecoder().decode([Manufacturer].self, from: data)
}
}