Search code examples
foreachswiftuipicker

Secondary Picker's 'ForEach' gives me Fatal Error: Index Out of Range


I get this error every time I launch a secondary picker.
The first picker works okay.
However when I switch pickers & scroll, I get the following:
enter image description here

Here is my entire code (written as a test of this problem):

import SwiftUI

struct ContentView: View {
    @State private var selectedItem = 0
    @State private var isMainPickerHidden = false
    @State private var isSecondaryPickerHidden = true

    var colors = ["Red", "Green", "Blue", "Tartan"]
    var sizes = ["Tiny", "Small", "Medium", "Large", "Super Size"]

    var body: some View {
        ZStack {
            Color.yellow.edgesIgnoringSafeArea(.all)
            ZStack {
                VStack {
                    Picker(selection: $selectedItem, label: Text("Please choose a color")) {
                        ForEach(colors.indices, id: \.self) {
                            Text(self.colors[$0])
                        }
                    }.hiddenConditionally(isHidden: isMainPickerHidden)

                    Text("You selected: \(colors[selectedItem])")
                        .hiddenConditionally(isHidden: isMainPickerHidden)
                }

                VStack {
                    Picker(selection: $selectedItem, label: Text("Please choose a size")) {
                        ForEach(sizes.indices, id: \.self) {
                            Text(self.sizes[$0])
                        }
                    }.hiddenConditionally(isHidden: isSecondaryPickerHidden)

                    Text("You selected: \(sizes[selectedItem])")
                        .hiddenConditionally(isHidden: isSecondaryPickerHidden)
                    Spacer()
                    Button(action: {
                        isSecondaryPickerHidden = !isSecondaryPickerHidden
                        isMainPickerHidden = !isMainPickerHidden
                    }) {
                        Text("Switch Pickers")
                    }.padding()
                }
            }
        }
    }
}

// =========================================================================================================

extension View {
    func hiddenConditionally(isHidden: Bool) -> some View {
        isHidden ? AnyView(hidden()) : AnyView(self)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enter image description here

What is the correct syntax for the ForEach{} to avoid this problem?


Solution

  • This is because you use the same selectedItem for both pickers.

    If in the second picker you select the last item (index 4) and then you switch to the first picker (max index = 3), then in this line:

    Text("You selected: \(colors[selectedItem])")
    

    you'll try accessing the index which is out of range.


    To fix this you can use a separate @State variable for each picker:

    struct ContentView: View {
        @State private var selectedColorIndex = 0
        @State private var selectedSizeIndex = 0
    
    Picker(selection: $selectedColorIndex, label: Text("Please choose a color")) {
    
    Picker(selection: $selectedSizeIndex, label: Text("Please choose a size")) {