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

Posted

技术标签:

【中文标题】macOS 上的 SwiftUI:带有详细视图和多选的列表【英文标题】:SwiftUI on macOS: list with detail view and multiple selection 【发布时间】:2021-01-20 15:06:12 【问题描述】:

TL;DR:

我无法在 macOS 上拥有包含详细视图和多项选择的列表。

更详细:

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

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

这是完整的代码:

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()
    

现在,到目前为止,在这方面取得了成功,我认为能够选择多行会很有用,所以我仔细研究了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)
            
        
    


现在的问题是我无法再选择行中的项目,无论是通过单击还是通过箭头键。这是我遇到的限制还是我“持有错误”?

【问题讨论】:

【参考方案1】:

使用按钮并将其插入集合中。键盘选择也适用于 shift +(向上/向下箭头)

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> = [] 

【讨论】:

这是一个想法。您可以为添加删除添加自己的逻辑。 它甚至比这简单得多——但你绝对把我推向了正确的方向,对此我更加感激!我的错误是使集合成为可选的,一旦我删除了一个以空集合开头的集合,它就会按预期工作。 @ASSeeger 只需提供空白默认值即可。因为在 nil 中它永远不会追加到 set need init。【参考方案2】:

实际上我的错误非常愚蠢——将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)
        
    


【讨论】:

以上是关于macOS 上的 SwiftUI:带有详细视图和多选的列表的主要内容,如果未能解决你的问题,请参考以下文章

在 MacOS 上的 SwiftUI 中查找点击位置

SwiftUI:在 macOS NavigationView 中关闭视图

使用 SwiftUI 的 macOS 应用程序中的导航视图不稳定

SwiftUI macOS 双击列表项

SwiftUI TextField (MacOS) 的奇怪行为

如何在 macOS 上检测 SwiftUI 中的键盘事件?