WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

`selection:` binding in `List`

Forums > SwiftUI

@roosterboy kindly fixed my enum issue with RawRepresentable, but now I'm really struggling to understand how the List(selection:) causes an update to the selection binding.

I am unpicking the Apple sample code at https://developer.apple.com/documentation/swiftui/building_a_great_mac_app_with_swiftui

There is a sidebar on the left, and the main detail view on the right. The ContentView looks like this:

struct ContentView: View {
    @EnvironmentObject var store: Store
    @SceneStorage("selection") private var selectedGardenID: Garden.ID?
    @AppStorage("defaultGarden") private var defaultGardenID: Garden.ID?

    var body: some View {
        NavigationView {
            Sidebar(selection: selection)
            GardenDetail(garden: selectedGarden)
        }
    }

    private var selection: Binding<Garden.ID?> {
        Binding(get: { selectedGardenID ?? defaultGardenID }, set: { selectedGardenID = $0 })
    }

    private var selectedGarden: Binding<Garden> {
        $store[selection.wrappedValue]
    }
}

and the Sidebar like this:

struct Sidebar: View {
    @EnvironmentObject var store: Store
    @SceneStorage("expansionState") var expansionState = ExpansionState()
    @Binding var selection: Garden.ID?

    var body: some View {
        List(selection: $selection) {
            DisclosureGroup(isExpanded: $expansionState[store.currentYear]) {
                ForEach(store.gardens(in: store.currentYear)) { garden in
                    SidebarLabel(garden: garden)
                        .badge(garden.numberOfPlantsNeedingWater)
                }
            } label: {
                Label("Current", systemImage: "chart.bar.doc.horizontal")
            }

            Section("History") {
                GardenHistoryOutline(range: store.previousYears, expansionState: $expansionState)
            }
        }
        .frame(minWidth: 250)
    }
}

Now the SidebarLabel view does not have any reference to the selection binding; yet, when you click on one of the labels, the selection updates and the binding propagates up to the ContentView and therefore the GardenDetai view is updated with the new selection.

My question is: how does clicking on one of the sidebar items cause this to happen, since there is no reference to the selection binding? Does the ForEach have anything to do with it?

If so, how can I add 'ad hoc' items that do not particpate in a ForEach like this? If not, what is going on??

   

In a way yes the ForEach does have something to do with it, because it is accessing store.gardens where store is an @EnvironmentObject, ie. something that can be accessed throughout the app's environment (where there is a suitable declaration).

EnvironmentObjects are linked to ObservableObject declarations, so any View accessing the EnvironmentObject is observing the object for changes, hence when you change it in one View, it is updated everywhere else it is accessed.

garden is just an instance of the ForEach loop, and SidebarLabel, updates garden through the Binding.

   

My question is: how does clicking on one of the sidebar items cause this to happen, since there is no reference to the selection binding? Does the ForEach have anything to do with it?

Behind the scenes, ForEach assigns a tag to each View it generates, based on the id provided, whether through an explicit id parameter or because the items being looped through conform to Identifiable. Then the Picker assigns the id of whichever View was selected to the bound selection variable. This is why the type of the tag, which cann either be assigned implicitly by the ForEach or explicitly using the .tag(_:) modifier, must match the type of the bound selection parameter.

   

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.