【问题标题】:SwiftUI How to create LazyVStack with selection as ListSwiftUI 如何创建选择列表的 LazyVStack
【发布时间】: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
    }
}

【问题讨论】:

    标签: swift iphone swiftui ios14


    【解决方案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)
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-04
      • 2021-04-30
      • 2013-03-09
      • 1970-01-01
      相关资源
      最近更新 更多