6

How do I build a dynamic list with @Binding-driven controls without having to reference the array manually? It seems obvious but using List or ForEach to iterate through the array give all sorts of strange errors.

struct OrderItem : Identifiable {
    let id = UUID()
    var label : String
    var value : Bool = false
}

struct ContentView: View {
    @State var items = [OrderItem(label: "Shirts"),
                        OrderItem(label: "Pants"),
                        OrderItem(label: "Socks")]
    var body: some View {
        NavigationView {
            Form {
                Section {
                    List {
                        Toggle(items[0].label, isOn: $items[0].value)
                        Toggle(items[1].label, isOn: $items[1].value)
                        Toggle(items[2].label, isOn: $items[2].value)
                    }
                }
            }.navigationBarTitle("Clothing")
        }
    }
}

This doesn't work:

            ...
                Section {
                    List($items, id: \.id) { item in
                        Toggle(item.label, isOn: item.value)
                    }
                }
            ...

Type '_' has no member 'id'

Nor does:

            ...
                Section {
                    List($items) { item in
                        Toggle(item.label, isOn: item.value)
                    }
                }
            ...

Generic parameter 'SelectionValue' could not be inferred

1
  • Solving this would answer the bigger objective: To create a ToggleList View that can take $items and manage, display the toggles. Commented Sep 7, 2019 at 19:51

2 Answers 2

11

Try something like

...
   Section {
       List(items.indices) { index in
           Toggle(self.items[index].label, isOn: self.$items[index].value)
       }
   }
...
Sign up to request clarification or add additional context in comments.

3 Comments

Wow. Can't believe I missed that. I was hoping I'd be able to pass an actual object but that gets me where I needed to go. Thanks!
I can't believe it!! It does work. Why would this work and passing the object doesn't??!!
This approach seems to break when the amount of items in the array gets reduced later. See stackoverflow.com/questions/57631225/…
9

While Maki's answer works (in some cases). It is not optimal and it's discouraged by Apple. Instead, they proposed the following solution during WWDC 2021:

Simply pass a binding to your collection into the list, using the normal dollar sign operator, and SwiftUI will pass back a binding to each individual element within the closure.

Like this:

struct ContentView: View {
    @State var items = [OrderItem(label: "Shirts"),
                        OrderItem(label: "Pants"),
                        OrderItem(label: "Socks")]

    var body: some View {
        NavigationView {
            Form {
                Section {
                    List($items) { $item in
                        Toggle(item.label, isOn: $item.value)
                    }
                }
            }.navigationBarTitle("Clothing")
        }
    }
}

3 Comments

Interesting. Is this limited to new APIs?
According to Matt Ricketson: "you can even back-deploy this code to any prior release supported by SwiftUI". In fact I think it uses a feature from SE-0293 (which is implemented in Swift 5.5). But only installing the 5.5 toolchain in the old (v12) Xcode is not enough. I think you will also need to add the init(projectedValue:) to Binding and make it conform to the RandomAccessCollection in order to use this tweak with older versions of SwiftUI.
I confirm this is the way to set an individual binding to each row dynamically. As a side note, l noticed that this doesn't work on Publisher wrappers. Only State wrappers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.