SwiftUI Picker with selection as struct
Asked Answered
C

4

6

Iam trying to use Picker as selection of struct. Let say I have a struct "Pet" like below

struct Pet: Identifiable, Codable, Hashable {

    let id = UUID()
    let name: String
    let age: Int
}

I am getting all Pet's from some class, where Pets are defined as @Published var pets = Pet

static let pets = Class().pets

I would like to be able to write a selection from picker to below variable:

@State private var petSelection: Pet?

Picker is:

Picker("Pet", selection: $petSelection){
                    ForEach(Self.pets) { item in
                        Text(item.name)

                    }
                }

Picker shows properly all avaliavble pets but when I chose one petSelection has been not changed (nil). How should I mange it?

Thanks!

Edit:

Of course I know that I can use tag like below:

Picker("Pet", selection: $petSelection) {
ForEach(0 ..< Self.pet.count) { index in
    Text(Self.pet[index].name).tag(index)
    }

But wonder is it possible to use struct as selection. Thanks

Charity answered 18/12, 2019 at 21:55 Comment(0)
H
23

Short answer: The type associated with the tag of the entries in your Picker (the Texts) must be identical to the type used for storing the selection.

From Picker docs:

Use an optional value for the selection input parameter. For that to work, you need to explicitly cast the tag modifier’s input as Optional to match. For an example of this, see tag(_:).

In your example: You have an optional selection (probably to allow "empty selection") of Pet?, but the array passed to ForEach is of type [Pet]. You have to add therefore a .tag(item as Pet?) to your entries to ensure the selection works.

ForEach(Self.pets) { item in
    Text(item.name).tag(item as Pet?)
}

Here follows my initial, alternate answer (getting rid of the optionality):

You have defined your selection as an Optional of your struct: Pet?. It seems that the Picker cannot handle Optional structs properly as its selection type.

As soon as you get rid of the optional for example by introducing a "dummy/none-selected Pet", Picker starts working again:

extension Pet {
    static let emptySelection = Pet(name: "", age: -1)
}

in your view initialise the selection:

@State private var petSelection: Pet = .emptySelection

I hope this helps you too.

Hogle answered 7/12, 2020 at 15:21 Comment(0)
F
4

You use the following way:

 @Published var  pets: [Pet?] = [ nil, Pet(name: "123", age: 23), Pet(name: "123dd", age: 243),]

     VStack{
      Text(petSelection?.name ??  "name")
      Picker("Pet", selection: $petSelection){
         ForEach(Self.pets, id: \.self) { item in
            Text(item?.name ?? "name").tag(item)
       }}}
Fidelafidelas answered 19/12, 2019 at 3:29 Comment(0)
A
2

the type of $petSelection in Picker(selection:[...] has to be the same type of id within your struct. So in your case you would have to change $petSelection to type if UUID since your items within the collection have UUID as identifier.

Anyway since this is not what you're after, but your intention is to receive the Pet as a whole when selected. For that you will need a wrapper containing Pet as the id. Since Pet is already Identifiable, there're only a few adjustments to do:

Create a wrapper having Pet as an id

struct PetPickerItem {
    let pet: Pet?
}

Now wrap all collection items within the picker item

Picker("Pet", selection: $petSelection) {
    ForEach(Self.pets.map(PetPickerItem.init), id: \.pet) {
        Text("\($0.pet?.name ?? "None")")
    }
}

You can now do minor adjustments like making PetPickerItem identifiable to remove the parameter id: from ForEach.

That's the best solution I came up with.

Access answered 9/2, 2020 at 16:37 Comment(0)
B
2

This is how I do it:

struct Language: Identifiable, Hashable  {
    var title: String
    var id: String
}


struct PickerView: View {

     var languages: [Language] = [Language(title: "English", id: "en-US"), Language(title: "German", id: "de-DE"), Language(title: "Korean", id: "ko-KR")]
     @State private var selectedLanguage = Language(title: "German", id: "de-DE")

     var body: some View {
        Picker(selection: $selectedLanguage, label: Text("Front Description")) {
            ForEach(self.languages, id: \.self) {language in
                Text(language.title)
           }
       }

}
Block answered 24/11, 2020 at 10:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.