I want to have a List that gives the user the ability to select items (and therefore use up/down keyboard navigation on the Mac to navigate the list). Due to the complexity of views, I am using children elements within the List that contain selectable items. For simplification purposes, this is a minimum example of what I’m trying to do.
struct ContentView: View {
@State private var selectedItem: Obj?
@State var demoData: [Obj] = [.init("Bardi"), .init("John")]
var body: some View {
List(selection: $selectedItem) {
VStack {
ForEach(demoData, id: \.self) { item in
Item(item: item)
}
}
}
}
}
struct Obj: Hashable {
var id: UUID = .init()
var name: String = ""
init(_ name: String) {
self.name = name
}
}
struct Item: View {
var item: Obj
var body: some View {
VStack {
Text(item.name)
}
}
}
The problem is the addition of the VStack breaks list selection. If I remove it, it works fine and as expected. I assume the VStack is preventing the List from properly detecting the individual items within the ForEach as selectable.
Is there any way to work around this? It seems a simple ask: I want a list with children subviews that categorise list items.
Only list rows can be selected. By wrapping your items in a VStack
, the entire VStack
becomes a single list row. You can add a tag
with an Obj
value to it, to be able to select the whole VStack
, but you cannot select the things inside the VStack
.
For example, one contains a list of tasks for the day as well as a TextField to add another task.
In that case you can make the TextField
a separate list row on its own.
List(selection: $selectedItem) {
ForEach(data, id: \.self) { item in
Item(item: item)
}
TextField("New Item", text: $newItemName)
}
The list row with the TextField
will not be selectable, because it has no tag
. Note that the ForEach
adds a tag
to its views, with the value of that item's id.
You also said,
I have different custom sections...
Great, then use Section
s! You even mentioned it yourself.
Presumably your VStack
s just contains the selectable items in the middle, then some unselectable views at the bottom (like the text field), and/or some unselectable views at the top. You can turn those unselectable views into the Section
's header and footer! Your VStack
s are basically reinventing Section
s.
Here is an example of a list with 2 sections:
List(selection: $selectedItem) {
// in reality you'd use a ForEach to "loop through" the sections you have, of course
Section {
ForEach(group1Data, id: \.self) { item in
Item(item: item)
}
} footer: {
TextField("New Item", text: $newGroup1Item)
}
Section {
ForEach(group2Data, id: \.self) { item in
Item(item: item)
}
} footer: {
TextField("New Item", text: $newGroup2Item)
}
}