Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to dynamically populate a SwiftUI Picker (segmented control)

I’ve got pickers working with a static list of items.

I am stuck on how to populate them using dynamic data (from an @state variable or an environment variable.)

This dirt-simple SwiftUI app creates a segmented control, but it doesn’t show a current selection and won’t allow the user to select one:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

import SwiftUI

struct ContentView: View {
    @State private var colors = ["Red", "Green", "Blue"]
    @State private var selectedColor: Int = 0
    
    var body: some View {
        VStack {
            HStack {
                Spacer().frame(width: 30)
                Picker("What is your favorite color?", selection: $selectedColor) {
                    ForEach(colors, id: \.self) { aValidColor in
                        Text(aValidColor)
                    }
                }
                Spacer().frame(width: 30)
            }
            .pickerStyle(.segmented)
            Spacer().frame(height: 30)
            Text("Value: \(colors[selectedColor])")
        }
    }
}

(I’m using Xcode 15.0 beta 3, if that matters.)

I gather it has something to do with the ids of the items in my ForEach and the tag values that get assigned to the Text items in the Picker, but I can’t figure out how to make it work.

Edit:

I got it to work by changing from an Array of String objects to an array of Identifiable Structs, and having the id of each struct be its index in the array:

import SwiftUI

struct AColor: Identifiable {
    let name: String
    let id: Int
}

struct ContentView: View {
    @State private var colors = [
        AColor(name: "Red", id: 0),
        AColor(name:"Green", id: 1),
        AColor(name:"Blue", id: 2)
    ]
    @State private var selectedColor: Int = 0
    
    var body: some View {
        VStack {
            HStack {
                Spacer().frame(width: 30)
                Picker("What is your favorite color?", selection: $selectedColor) {
                    ForEach(colors) { aValidColor in
                        Text(aValidColor.name)
                    }
                }
                Spacer().frame(width: 30)
            }
            .pickerStyle(.segmented)
            Spacer().frame(height: 30)
            Text("Value: \(colors[selectedColor].name)")
        }
    }
}

That is less than ideal though, since now I have to maintain an array of structs where each item in the array has a value that contains its index in the array. Ugh.

It seems like there should be some way to use enumerated() to get an array of objects and their indexes, but ForEach wants a Binding, not an enumerated sequence. (I’m still trying to wrap my head around bindings.)

>Solution :

The array contains strings but the Binding is to an Int.

So this would be another way to fix it:

ForEach(Array(colors.enumerated()), id: \.element) { index, color in
    Text(color).tag(index)
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading