2

Original problem: I need to load 3 pickers, with the selection choices of each subsequent picker dependent upon the ones before it – and with each of the subsequent arrays generated dynamically based upon (a) the prior choices and (b) a database of some 500 objects.

  • Picker one works fine because its array of options does not change and is only dependent upon the data file.
  • Picker two works fine because it updates strictly in accordance with picker 1.
  • Picker three, however, needs to know the choice in picker 2 in order to generate its options. But the options in picker 2 are generated dynamically from the choice in picker 1.

Therefore, if the choice in picker 1 changes such that the length of the array (of choices) in picker 2 is smaller than the specific choice being fed to picker 3, the program crashes with an out of range error. I completely understand why the crash is occurring (it's clear). But it seems like what I'm trying to achieve here would be fairly common and that there should be a solution. All the solutions I could find deal with instances where the picker arrays could be fixed in advance (e.g. the Country/City example that comes up repeatedly).

fronesis
  • 99
  • 1
  • 6
  • There's no straightforward solution here. You need to avoid a case where you're accessing an array at the wrong index. I would suggest having a view model with the `@Published` arrays needed for each picker properly populated on each change. And then iterate over the arrays, instead of over indices: `ForEach(models, id: \.self) { ... }`. Basically, design your model as if there were no pickers - just some functions that change selection, and make *that* work; then hook it up to pickers – New Dev Aug 03 '20 at 20:04
  • Thanks for the reply. What you say makes sense, although I'm having the hardest time trying to implement it. I can create @Published arrays that are empty, but I think what I want is working, filled arrays that update. However, whenever I try (working outside of my views) to fill them the arrays with data by calling my functions , I end up with compiler errors regarding using self before initialization. I'm very new to swift and I'm really at a loss as to when and where I can call functions: I can't do it within a class, but I don't want to do it in a view. This leaves me at a loss. – fronesis Aug 03 '20 at 23:34

1 Answers1

2

Answering my own question here, and updating since I found a simpler, more stable solution. The solution requires a number of elements:

  1. As New Dev helpfully suggested, the data structure has to be constructed so that the picker elements are connected to each other. I had originally derived three separate arrays in distinct. When one updated dynamically there was nothing to track their relation. So I created a data structure in which higher choices (e.g. Brands) contained lower choices (e.g. Models and Years).
  2. In addition, the indices for the arrays cannot be directly bound to the choices in each picker. The solution here is to create a second set of binding Ints, following the answer here: SwiftUI Picker onChange or equivalent?

Here's the final result, which I've now tested extensively and it's completely stable.

struct ContentView: View {

    @State private var brands: [Brand] = getBrands()
    @State private var choice1 = 0
    @State private var coice2 = 0
    @State private var choice3 = 0

    var body: some View {
        
        let chosenBrand = Binding<Int>(get: {
                    return self.choice1
                }, set: {
                    self.choice1 = $0
                    self.choice2 = 0
                    self.choice3 = 0
                })
        
        let chosenModel = Binding<Int>(get: {
                    return self.choice2
                }, set: {
                    self.choice2 = $0
                    self.choice3 = 0
                })
        
        let chosenYear = Binding<Int>(get: {
                    return self.choice3
                }, set: {
                    self.choice3 = $0
                })
    return
        VStack {
            Picker(selection: chosenBrand, label: Text("Brand")) {
                ForEach(self.brands.indices, id: \.self) { index in
                    Text(self.brands[index].name).tag(index)
                    }
                }

            Picker(selection: chosenModel, label: Text("Model")) {
                ForEach(self.brands[choice1].models.indices, id: \.self) { index in
                    Text(self.brands[self.choice1].models[index].name).tag(index)
                    }
                }

            Picker(selection: chosenYear, label: Text("Year")) {
                ForEach(self.brands[choice1].models[choice2].years).indices, id: \.self) { index in
                    Text(self.brands[self.choice1].models[self.choice2].years[index].description).tag(index)
                    }
                }
            }
        }
    }
fronesis
  • 99
  • 1
  • 6