SwiftUI 如何创建选择列表的 LazyVStack
Posted
技术标签:
【中文标题】SwiftUI 如何创建选择列表的 LazyVStack【英文标题】:SwiftUI How to create LazyVStack with selection as List 【发布时间】:2020-10-24 11:01:39 【问题描述】:我想在LazyVStack
中有类似List(selection: )
的东西。
问题是我不知道如何管理内容以拆分它包含的每个元素。
我尝试做的事情:
public struct LazyVStackSelectionable<SelectionValue, Content> : View where SelectionValue : Hashable, Content : View
let content: Content
var selection: Binding<Set<SelectionValue>>?
@Environment(\.editMode) var editMode
public init(selection: Binding<Set<SelectionValue>>?, @ViewBuilder content: () -> Content)
self.content = content()
self.selection = selection
public var body: some View
if self.editMode?.wrappedValue == EditMode.active
HStack
content //here I would like to have something like ForEach (content, id:\.self)
Button(action:
//add the UUID to the list of selected item
)
Image(systemName: "checkmark.circle.fill")
//Image(systemName: selection?.wrappedValue.contains(<#T##member: Hashable##Hashable#>) ? "checkmark.circle.fill" : "circle")
else
content
struct ListView: View
@State private var editMode: EditMode = .inactive
@State private var selection = Set<UUID>()
@State private var allElements: [MyElement] = [MyElement(id: UUID(), text: "one"),
MyElement(id: UUID(), text: "two" ),
MyElement(id: UUID(), text: "tree" )
]
var body: some View
NavigationView
VStack
Divider()
Text("LazyVStack")
.foregroundColor(.red)
LazyVStack
ForEach(allElements, id: \.self) element in //section data
Text(element.text)
Divider()
Text("LazyVStackSelectionable")
.foregroundColor(.red)
LazyVStackSelectionable(selection: $selection)
ForEach(allElements, id: \.self) element in //section data
Text(element.text)
Divider()
.environment(\.editMode, self.$editMode)
.navigationBarTitle(Text("LIST"), displayMode: .inline)
.navigationBarItems(//EDIT
trailing:
Group
HStack (spacing: 15)
self.editButton
self.delInfoButton
.contentShape(Rectangle())
)
//MARK: EDIT MODE
private func deleteItems()
DispatchQueue.global(qos: .userInteractive).async
Thread.current.name = #function
selection.forEach idToRemove in
if let index = allElements.firstIndex(where: $0.id == idToRemove )
allElements.remove(at: index)
private var editButton: some View
Button(action:
self.editMode.toggle()
self.selection = Set<UUID>()
)
Text(self.editMode.title)
private var delInfoButton: some View
if editMode == .inactive
return Button(action: )
Image(systemName: "square.and.arrow.up")
else
return Button(action: deleteItems)
Image(systemName: "trash")
struct ListView_Previews: PreviewProvider
static var previews: some View
ListView()
edit = .inactive
edit = .active
更新
使用 Asperi 的解决方案,我失去了 LazyVStack 的适当性,如果不显示所有行也会被加载(并且也不能滚动:
struct SampleRow: View
let number: Int
var body: some View
Text("Sel Row \(number)")
init(_ number: Int)
print("Loading LazySampleRow row \(number)")
self.number = number
struct LazySampleRow: View
let number: Int
var body: some View
Text("LVS element \(number)")
init(_ number: Int)
print("Loading LazyVStack row \(number)")
self.number = number
var aLotOfElements: [MyElement]
var temp: [MyElement] = []
for i in 1..<200
temp.append(MyElement(id: UUID(), number: i))
return temp
struct ContentView: View
@State private var editMode: EditMode = .inactive
@State private var selection = Set<UUID>()
@State private var allElements: [MyElement] = aLotOfElements//[MyElement(id: UUID(), number: 1)]
var body: some View
NavigationView
HStack
VStack
Text("LazyVStack")
.foregroundColor(.red)
ScrollView
LazyVStack (alignment: .leading)
ForEach(allElements, id: \.self) element in //section data
LazySampleRow(element.number)
Divider()
VStack
LazyVStack (alignment: .leading)
Divider()
Text("LazyVStackSelectionable")
.foregroundColor(.red)
LazyVStackSelectionable(allElements, selection: $selection) element in
SampleRow(element.number)
Divider()
.environment(\.editMode, self.$editMode)
.navigationBarTitle(Text("LIST"), displayMode: .inline)
.navigationBarItems(//EDIT
trailing:
Group
HStack (spacing: 15)
self.editButton
self.delInfoButton
.contentShape(Rectangle())
)
//MARK: EDIT MODE
private func deleteItems()
DispatchQueue.global(qos: .userInteractive).async
Thread.current.name = #function
selection.forEach idToRemove in
if let index = allElements.firstIndex(where: $0.id == idToRemove )
allElements.remove(at: index)
private var editButton: some View
Button(action:
self.editMode.toggle()
self.selection = Set<UUID>()
)
Text(self.editMode.title)
private var delInfoButton: some View
if editMode == .inactive
return Button(action: )
Image(systemName: "square.and.arrow.up")
else
return Button(action: deleteItems)
Image(systemName: "trash")
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView()
extension EditMode
var title: String
self == .active ? NSLocalizedString("done", comment: "") : NSLocalizedString("edit", comment: "")
mutating func toggle()
self = self == .active ? .inactive : .active
【问题讨论】:
【参考方案1】:您需要为所需内容类型的所有变体创建自定义处理的容器。
以下是以下内容支持示例的可能方向演示(以List
为例)
LazyVStackSelectionable(allElements, selection: $selection) element in
Text(element.text)
使用 Xcode 12 / ios 14 准备和测试的演示(它使用了一些 SwiftUI 2.0 功能,因此如果需要 SwiftUI 1.0 支持,需要进行更多调整)
struct LazyVStackSelectionable<SelectionValue, Content> : View where SelectionValue : Hashable, Content : View
@Environment(\.editMode) var editMode
private var selection: Binding<Set<SelectionValue>>?
private var content: () -> Content
private var editingView: AnyView?
init(selection: Binding<Set<SelectionValue>>?, @ViewBuilder content: @escaping () -> Content)
self.selection = selection
self.content = content
var body: some View
Group
if editingView != nil && self.editMode?.wrappedValue == .active
editingView!
else
self.content()
extension LazyVStackSelectionable
init<Data, RowContent>(_ data: Data, selection: Binding<Set<SelectionValue>>?, @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent) where Content == ForEach<Data, Data.Element.ID, HStack<RowContent>>, Data : RandomAccessCollection, RowContent : View, Data.Element : Identifiable, SelectionValue == Data.Element.ID
self.init(selection: selection, content:
ForEach(data) el in
HStack
rowContent(el)
)
editingView = AnyView(
ForEach(data) el in
HStack
rowContent(el)
if let selection = selection
Button(action:
if selection.wrappedValue.contains(el.id)
selection.wrappedValue.remove(el.id)
else
selection.wrappedValue.insert(el.id)
)
Image(systemName: selection.wrappedValue.contains(el.id) ? "checkmark.circle.fill" : "circle")
)
【讨论】:
非常聪明的解决方案。但是检查我的更新,使用您的解决方案,所有行都已加载,我丢失了 LazyVStack 属性。【参考方案2】:我建议修改ContentView
并将绑定传递给它,而不是创建自定义LazyVStack
。
struct SampleRow: View
let element: MyElement
let editMode: Binding<EditMode>
let selection: Binding<Set<UUID>>?
var body: some View
HStack
if editMode.wrappedValue == .active,
let selection = selection
Button(action:
if selection.wrappedValue.contains(element.id)
selection.wrappedValue.remove(element.id)
else
selection.wrappedValue.insert(element.id)
)
Image(systemName: selection.wrappedValue.contains(element.id) ? "checkmark.circle.fill" : "circle")
Text("Sel Row \(element.number)")
init(_ element: MyElement,
editMode: Binding<EditMode>,
selection: Binding<Set<UUID>>?)
print("Loading LazySampleRow row \(element.number)")
self.editMode = editMode
self.element = element
self.selection = selection
然后你可以将普通的LazyVStack
包裹在ScrollView
中来实现你所需要的。
ScrollView
LazyVStack(alignment: .leading)
ForEach(allElements, id: \.self)
SampleRow($0,
editMode: $editMode,
selection: $selection)
【讨论】:
以上是关于SwiftUI 如何创建选择列表的 LazyVStack的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 如何在其中创建带有自定义 UIViews 的列表