SwiftUI:点击时更改列表中项目的背景颜色

SwiftUI: change background color of item in a list when it's tapped on

我有一个带有项目列表的水平滚动视图,我想在用户点击它时更改项目的背景颜色。这是我的代码,但是当我 运行 它并点击项目时没有任何反应。

struct HorizontalList: View {
    var list = ["item 1", "item 2", "item 3", "item 4", "item 5", "item 6", "item 7", "item 8", "item 9", "item 10"]
    @State var selectedIndex = 0
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 10) {
                ForEach(0..<list.count) { index in
                    ListItem(isSelected: selectedIndex == index, label: list[index])
                        .listRowBackground(Color.blue)
                        .onTapGesture {
                            selectedIndex = index
                        }
                }
            }
        }
    }
}

struct ListItem: View {
    @State var isSelected: Bool
    @State var label: String
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8)
                .foregroundColor(isSelected ? Color.blue : Color.clear)
                .frame(minHeight: 16, idealHeight: 16, maxHeight: 16)
            Text(label)
        }
    }
}

您需要使用 .simultaneousGesture,因为列表在幕后使用 DragGesture

        .simultaneousGesture(
            TapGesture()
                .onEnded({ _ in
                    selectedIndex = index
                })
        )

编辑: 查看代码中的注释。

struct HorizontalList: View {
    var listItems = Array(1...10).map( { ListItem(text: "Item \([=11=])") })
    @State var selectedIndex = 0
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 10) {
                // It is good practice to always supply a ForEach with an identifiable. If you were
                // to try to delete an element in the array, you can cause a crash without this.
                // What Array(zip()) does is takes listItems and listItems.indices and put them together
                // into an array of pairs, requiring two arguments in the ForEach, one for the item and
                // one for the index. You can then use both in the loop. However, the ForEach tracks them by
                // the identifiable element, not the index which is what the id: \.0 does.
                ForEach(Array(zip(listItems, listItems.indices)), id: \.0) { item, index in
                    ListItemView(isSelected: selectedIndex == index, label: item.text)
                        .listRowBackground(Color.blue)
                        .simultaneousGesture(
                            TapGesture()
                                .onEnded({ _ in
                                    selectedIndex = index
                                })
                        )
                }
            }
        }
    }
}

struct ListItemView: View {
    // There is no reason for these to be an @State var unless this view changes them. If it does,
    // it really should be a Binding to pass the data back.
    let isSelected: Bool
    let label: String
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8)
                .foregroundColor(isSelected ? Color.blue : Color.clear)
                .frame(minHeight: 16, idealHeight: 16, maxHeight: 16)
            Text(label)
        }
    }
}

// This becomes the item you itereate on
struct ListItem:Hashable, Identifiable {
    let id = UUID()
    var text: String
}