在 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
并且你会在初始化程序中传递几个额外的变量,例如 Data
和 MaxSelectionSize
并注意控制自定义视图中的选择,使其可在不同项目中重复使用 List
。但这超出了这个问题的范围。
我正在尝试找到一种方法来限制列表中 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
并且你会在初始化程序中传递几个额外的变量,例如 Data
和 MaxSelectionSize
并注意控制自定义视图中的选择,使其可在不同项目中重复使用 List
。但这超出了这个问题的范围。