macOS 上的 SwiftUI:带有详细视图和多项选择的列表

SwiftUI on macOS: list with detail view and multiple selection

TL;DR:

我无法在 macOS 上获得包含详细视图和多个 selection 的列表。

更详细:

为了演示我的问题,我制作了一个小示例项目。 UI 看起来如下:

这是启动时的“应用程序”,顶部是一个列表,下面是详细表示。因为我正在使用 List 的初始化程序 init(_:selection:rowContent:),根据 Apple 的文档,其中 selection 的类型为 Binding<SelectionValue?>?,所以我可以免费使用键盘箭头键 selecting 项目。

完整代码如下:

import SwiftUI

@main
struct UseCurorsInLisstApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(ViewModel())
        }
    }
}

class ViewModel: ObservableObject {
    @Published var items = [Item(), Item(), Item(), Item(), Item()]
    @Published var selectedItem: Item? = nil
}

struct Item: Identifiable, Hashable {
    let id = UUID()
}

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel
    
    var body: some View {
        VStack {
            
            List(vm.items, id: \.self, selection: $vm.selectedItem) { item in
                VStack {
                    Text("Item \(item.id.uuidString)")
                    Divider()
                }
            }
            
            Divider()
            
            Group {
                if let item = vm.selectedItem {
                    Text("Detail item \(item.id.uuidString)")
                } else {
                    Text("No selection…")
                }
            }
            .frame(minHeight: 200.0, maxHeight: .infinity)
            
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

现在,到目前为止取得了成功,我认为能够 select 不止一行会很有用,所以我仔细研究了 List(_:selection:rowContent:),其中 selection 的类型是 Binding<Set<SelectionValue>>?。为了能够有一个详细视图,我只是对

做了一些小改动

ViewModel:

class ViewModel: ObservableObject {
    @Published var items = [Item(), Item(), Item(), Item(), Item()]
    @Published var selectedItem: Item? = nil
    
    @Published var selectedItems: Set<Item>? = nil {
        didSet {
            if selectedItems?.count == 1, let item = selectedItems?.first {
                selectedItem = item
            }
        }
    }
}

ContentView

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel
    
    var body: some View {
        VStack {
            
            List(vm.items, id: \.self, selection: $vm.selectedItems) { item in
                VStack {
                    Text("Item \(item.id.uuidString)")
                    Divider()
                }
            }
            
            Divider()
            
            Group {
                if vm.selectedItems?.count == 1, let item = vm.selectedItems?.first {
                    Text("Detail item \(item.id.uuidString)")
                } else {
                    Text("No or multiple selection…")
                }
            }
            .frame(minHeight: 200.0, maxHeight: .infinity)
            
        }
    }
}

现在的问题是我无法再select 行中的项目,既不能通过单击,也不能通过箭头键。这是我 运行 的限制还是我“持有错误”?

使用按钮并将其插入集合。键盘选择也适用于 shift + (up/down arrow)

class ViewModel: ObservableObject {
    @Published var items = [Item(), Item(), Item(), Item(), Item()]
    @Published var selectedItem: Item? = nil
    
    @Published var selectedItems: Set<Item> = []
}

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel
    
    var body: some View {
        VStack {
            
            List(vm.items, id: \.self, selection: $vm.selectedItems) { item in
                Button {
                    vm.selectedItem = item
                    vm.selectedItems.insert(item)
                } label: {
                    VStack {
                        Text("Item \(item.id.uuidString)")
                        Divider()
                    }
                }
                .buttonStyle(PlainButtonStyle())
            }
            
            Divider()
            
            Group {
                if let item = vm.selectedItem {
                    Text("Detail item \(item.id.uuidString)")
                } else {
                    Text("No or multiple selection…")
                }
            }
            .frame(minHeight: 200.0, maxHeight: .infinity)
            
        }
    }
}

添加删除:

Button {
    vm.selectedItem = item
    if vm.selectedItems.contains(item) {
        vm.selectedItems.remove(item)
    } else {
        vm.selectedItems.insert(item)
    }
}

编辑 简单的需要给一个空的默认值来设置。因为在 nil 中它永远不会附加到需要初始化的集合中。

@Published var selectedItems: Set<Item> = [] {

实际上我的错误非常愚蠢——将 selectedItems-set 设置为可选会阻止列表正常工作。感谢@Raja Kishan,他用他的建议将我推向了正确的方向。

这是完整的工作代码:

import SwiftUI

@main
struct UseCurorsInLisstApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(ViewModel())
        }
    }
}

class ViewModel: ObservableObject {
    @Published var items = [Item(), Item(), Item(), Item(), Item()]
    @Published var selectedItems = Set<Item>()
}

struct Item: Identifiable, Hashable {
    let id = UUID()
}

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel
    
    var body: some View {
        VStack {
            List(vm.items, id: \.self, selection: $vm.selectedItems) { item in
                VStack {
                    Text("Item \(item.id.uuidString)")
                    Divider()
                }
            }
            
            Divider()
            
            Group {
                if vm.selectedItems.count == 1, let item = vm.selectedItems.first {
                    Text("Detail item \(item.id.uuidString)")
                } else {
                    Text("No or multiple selection…")
                }
            }
            .frame(minHeight: 200.0, maxHeight: .infinity)
        }
    }
}