首页 > 解决方案 > 具有切换子视图和选择手势的列表项

问题描述

我有一个列表和包含Toggle视图的项目。我想分别处理选择和切换状态。未选择列表项时应禁用切换。问题是:

  1. 第一次点击 - 选择列表项。( selected = true)
  2. 点击切换 - 设置selection = false

在 UIKit 上,Button/Switch/etc... 抓住“点击”,不要进入 TableView 选择状态。

struct ListView: View {
    @State var selected = Set<Int>()
    let items = (1...10).map { ItemDataModel(id: $0) }
    
    var body: some View {
        List(items) { item in
            ListItemView(dataModel: item)
                .onTapGesture { if !(selected.remove(item.id) != .none) { selected.insert(item.id) }}
        }
    }
}

struct ListItemView: View {
    @ObservedObject var dataModel: ItemDataModel
    
    var body: some View {
        HStack {
            Text(dataModel.title)
            Spacer()
            Toggle(
                isOn:Binding<Bool>(get: {dataModel.isOn}, set: {dataModel.isOn = $0}),
                label: { Text("toggle") })
        }
    }
}

class ItemDataModel: Hashable, Identifiable, ObservableObject {
    let id: Int
    let title: String
    @Published var isOn: Bool = false
    
    init(id: Int) {
        self.id = id
        title = "item \(id)"
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
    }
    
    static func == (lhs: ItemDataModel, rhs: ItemDataModel) -> Bool {
        return lhs.id == rhs.id
    }
}

[请尽量忽略语法错误(如果存在)并专注于手势问题]

标签: iosswiftuiswiftui-list

解决方案


首先,你的[ItemDataModel]数组应该移到身体之外,所以它不会每次都重新创建:

struct ListView: View {
    @State var selected = Set<Int>()
    let items = (1...10).map(ItemDataModel.init) // move outside the `body`

    var body: some View {
        VStack {
            Text(String(describing: selected))
            List(items) { item in
                ListItemView(dataModel: item)
                    .onTapGesture { if !(selected.remove(item.id) != .none) { selected.insert(item.id) }}
            }
        }
    }
}

然后,确保Togglein yourListItemView不会占用所有空间(这是默认行为)并附onTapGesture加以覆盖父母的手势:

struct ListItemView: View {
    @ObservedObject var dataModel: ItemDataModel

    var body: some View {
        HStack {
            Text(dataModel.title)
            // Text("toggle") // if necessary add Toggle's label as `Text`
            Spacer()
            Toggle("", isOn: $dataModel.isOn) // use another initialiser
                .fixedSize() // limit Toggle's width
                .background(Color.red) // debug-only, to see the real frame
                .onTapGesture {} // override tap gestures
        }
        .contentShape(Rectangle()) // make empty space *clickable*
    }
}

推荐阅读