How to change and use a State variable with Picker?

I seem to struggle with Picker. Having read the documentation, I still feel that I'm missing something.

In my project, I want to be able to switch between tabs of content. As the tabs are mutually exclusive, the Picker seems as the best choice.

Say, my Tab Is declared as such:

struct Tab: Identifiable, Hashable {
    var id: UUID
    var name: String
}

Then, as reprex, let's say I want to display the name of the tab I've chosen. After trying to get the binding on the Tab element itself, I've found this answer on SO and tried to achieve it like this:

struct Test: View {
    @State private var selectedTabIndex: Int = 0
    var tabs: [Tab] = ["Work", "Hobby", "Random"].map {Tab(id: UUID(), name: $0)}
    var body: some View {
        VStack {
            Picker("Tab", selection: $selectedTabIndex) {
                ForEach(tabs) {tab in
                    Text(tab.name)
                }
            }
            Text(tabs[selectedTabIndex].name)
        }
    }
}

This example results in the displayed name not updating. If I try to select the tab using the bounded syntax, i.e. tabs[$selectedTabIndex].name, I get type mismatch between types - Cannot convert value of type 'Binding<Int>' to expected argument type 'Int'. Which is understandable, but does not get me any closer.

What should I do to get this to work?

Is the index approach feasible? If so, why does the view not update? What should I read about?

Answered by jordan-na-dzielni in 715321022

Although @Claude31 example works, I realized that there is a better way to tackle the problem.

As the tag only provides conformance to the bound var type, switching type of id from UUID to Self results in ForEach tagging the entries of Picker automatically. I don't need the redundant property, everyone is happy.

So, my solution is (cumbersomely written)

struct Tab: Identifiable, Hashable {
    var id: Self { self }
    var name: String
}

extension Tab {
    var sampleData: [Tab] = ["Work", "Hobby", "Random"].map {Tab(name: $0)}
}

struct Test: View {
    @State private var selectedTab: Tab = Tab.sampleData[0]
    var body: some View {
        VStack {
            Picker("Tab", selection: $selectedTab) {
                ForEach(Tab.sampleData) {tab in
                    Text(tab.name)
                }
            }
            Text(selectedTab.name)
        }
    }
}

Unless there is some UUID superiority over Self as an identifier...

You need to set a tag for each item in Picker.

This works:

struct Tab: Identifiable, Hashable {
    var id: UUID
    var name: String
    var row: Int
}

struct Test: View {
    @State private var selectedTabIndex: Int = 0
    var tabs: [Tab] = ["Work", "Hobby", "Random"].enumerated().map { (index, item) in Tab(id: UUID(), name: item, row: index) }
    
    var body: some View {
        VStack {
            Picker("Tab", selection: $selectedTabIndex) {
                ForEach(tabs) {tab in
                    Text(tab.name).tag(tab.row)
                }
            }
            Text(tabs[selectedTabIndex].name)
        }
    }
}
Accepted Answer

Although @Claude31 example works, I realized that there is a better way to tackle the problem.

As the tag only provides conformance to the bound var type, switching type of id from UUID to Self results in ForEach tagging the entries of Picker automatically. I don't need the redundant property, everyone is happy.

So, my solution is (cumbersomely written)

struct Tab: Identifiable, Hashable {
    var id: Self { self }
    var name: String
}

extension Tab {
    var sampleData: [Tab] = ["Work", "Hobby", "Random"].map {Tab(name: $0)}
}

struct Test: View {
    @State private var selectedTab: Tab = Tab.sampleData[0]
    var body: some View {
        VStack {
            Picker("Tab", selection: $selectedTab) {
                ForEach(Tab.sampleData) {tab in
                    Text(tab.name)
                }
            }
            Text(selectedTab.name)
        }
    }
}

Unless there is some UUID superiority over Self as an identifier...

How to change and use a State variable with Picker?
 
 
Q