在 SwiftUI 列表编辑模式中禁用对单独行的编辑

Disable edit on separate rows in SwitUI List EditMode

我正在尝试找到一种方法来限制列表中 select 可用的项目数(使用 EditMode),假设我有一个包含 6 个项目的列表并且我希望用户能够 select 其中 3 个,当 3 个被 selected 时,我想禁用那些没有被 selected 的。但是,我希望被 selected 的项目继续被取消 selectable,这样其他项目就可以被 selected 了。 selectedItems 集稍后转换为数据模型并保存到数据库。

struct SelectItemListView: View {

var items = ["one", "two", "three", "four", "five", "six"]
var numberOfselectedItems = 3 // <- controlled by user input in real project
@State var selectedItems = Set<String>()

var body: some View {
    List(items, id: \.self, selection: $selectedItems) { item in // <- id is \.id from datamodel in real project
        Text(item)
    }
    .disabled(selectedItems.count >= numberOfselectedItems)

    .environment(\.editMode, .constant(EditMode.active))
}
}

代码可以编译,但会禁用整个列表,而不是个别行。这在 SwiftUI 中甚至可能吗?

不幸的是,据我所知,目前还没有办法开箱即用。

但是,可以通过创建一个自定义 ListItemRow 来实现,该自定义 ListItemRow 可以接受任何内容(基本上是通用内容),但它会负责选择和取消选择行。

struct CustomListRow<Content> : View where Content : View {
    @Binding var isSelectable: Bool
    @State private var isSelected: Bool = false
    
    var onSelectionChanged :(_ isSelected:Bool) -> () = {_ in}
    
    var content: () -> Content
    
    var body: some View {
        HStack {
            Circle()
                .overlay(
                    Circle()
                        .stroke(self.isSelected ? Color.blue : Color(red: 0.741, green: 0.741, blue: 0.749), lineWidth: 1)
                        .overlay(Image(systemName: "checkmark").resizable().scaledToFit().frame(width: 12, height: 12).foregroundColor(Color.white))
            )
                .frame(width: 20, height: 20, alignment: .center)
                .foregroundColor(self.isSelected ? Color.blue : Color.clear)
            
            self.content()
        }
        .onTapGesture {
            if(self.isSelectable) {
                self.isSelected.toggle()
                self.onSelectionChanged(self.isSelected)
            }
        }
    }
    
    func onSelectionChanged(callback: @escaping (_ isSelected: Bool) -> ()) -> some View {
        CustomListRow(isSelectable: self.$isSelectable, onSelectionChanged: callback, content: self.content)
    }
}

这是使用方法

struct ContentView: View {
    
    var items = ["one", "two", "three", "four", "five", "six"]
    var numberOfselectedItems = 3 // <- controlled by user input in real project
    @State var selectedItems = Set<String>()
    
    var body: some View {
        List(items, id: \.self) { item in // <- id is \.id from datamodel in real project
            CustomListRow(isSelectable: Binding(get: {
                return (self.itemExist(item: item) || self.selectedItems.count < self.numberOfselectedItems)
            }, set: { _ in})) {
                Text("item: \(item) - Exist: \(self.itemExist(item: item) ? "true" : "false") - count: \(self.selectedItems.count)")
            }
            .onSelectionChanged { isSelected in
                if(isSelected == true) {
                    self.selectedItems.insert(item)
                } else {
                    self.selectedItems.remove(item)
                }
            }
        }
    }
    
    func itemExist(item: String) -> Bool{
        return self.selectedItems.contains(item)
    }
}

你可以更进一步,创建一个自定义 List 来使用这个 CustomListItemRow 并且你会在初始化程序中传递几个额外的变量,例如 DataMaxSelectionSize 并注意控制自定义视图中的选择,使其可在不同项目中重复使用 List。但这超出了这个问题的范围。