无法让 SwiftUI 列表选择与自定义结构一起使用

Can't get SwiftUI List selection to work with custom struct

我正在尝试列出最喜欢的报纸。在编辑模式下,列表显示所有可用的报纸,用户可以从中 select 他的最爱。 select收藏夹后,列表仅显示收藏夹。这是我的代码:

 struct Newspaper: Hashable {
     let name: String
 }
 
 struct ContentView: View {
     @State var editMode: EditMode = .inactive
     @State private var selection = Set<Newspaper>()
     var favorites: [Newspaper] {
         selection.sorted(by: ({ [=10=].name < .name }))
     }
     
     let newspapers = [
         Newspaper(name: "New York Times"),
         Newspaper(name: "Washington Post")
     ]
     
     var body: some View {
         NavigationView {
             List(editMode == .inactive ? favorites : newspapers, id: \.name, selection: $selection) { aliasItem in
                 Text(aliasItem.name)
             }
             .toolbar {
                 EditButton()
             }
             .environment(\.editMode, self.$editMode)
         }
     }
 }

问题是列表进入了编辑模式,但是 selection 小部件没有出现。如果我将 Newspaper 替换为 String 的数组(并相应地修改其余代码),那么 selection 小部件确实会出现并且列表按预期工作。谁能解释一下问题出在哪里?

我最初尝试使用这样的 Identifiable 报纸:

 struct Newspaper: Codable, Identifiable, Equatable, Hashable {
     var id: String { alias + publicationName }
     let alias: String
     let publicationName: String
 }

由于这不起作用,我测试了上面的简单版本以试图找出问题所在。

由于我需要保存收藏夹,报纸必须是可编码的,因此不能使用 UUID,因为它们是从磁盘读取的,并且完整的报纸数组是从服务器获取的。这就是为什么我将 id 作为计算的 属性.

Yrb:s 回答提供了问题的解决方案:selection Set 的类型必须与您在 Identifiable 中使用的 id 类型相同结构,而不是您在 List.

中显示的类型

所以在我的例子中(Identifiable 报纸版)selection Set 必须是 Set<String> 类型而不是 Set<Newspaper> 因为 id 的报纸是 String.

您的问题源于 List's 选择模式使用 id: 属性 来跟踪您的选择。由于您将 List 声明为 List(..., id: \.name, ...),因此您的 selection var 需要是 String 类型。如果将其更改为 List(..., id: \.self, ...),它会起作用,但在这样的列表中使用 self 会带来自己的问题。为了与最佳实践保持一致,暂时忘记选择,您应该使用 Identifiable 结构。 List 然后应该通过结构上的 id 参数来标识元素。 (我用的是UUID)

进行选择,这意味着您需要将其定义为 @State private var selection = Set<UUID>()。剩下的就是处理您的 favorites 计算变量。无需返回 selection 的数组,您只需过滤 newspapers 数组以获取 selection 中包含的那些元素。最后,这给你留下了这个:

struct Newspaper: Identifiable, Comparable {
    let id = UUID()
    let name: String
    
    static func < (lhs: Newspaper, rhs: Newspaper) -> Bool {
        lhs.name < rhs.name
    }
}

 struct ContentView: View {
    @State var editMode: EditMode = .inactive
    @State private var selection = Set<UUID>()
    var favorites: [Newspaper] {
        newspapers.filter { selection.contains([=10=].id) }
    }
    
    let newspapers = [
        Newspaper(name: "New York Times"),
        Newspaper(name: "Washington Post")
    ]
    
    var body: some View {
        NavigationView {
            VStack {
                List(editMode == .inactive ? favorites.sorted() : newspapers, selection: $selection) { aliasItem in
                    Text(aliasItem.name)
                }
                .toolbar {
                    EditButton()
                }
                .environment(\.editMode, self.$editMode)
                Text(selection.count.description)
            }
        }
    }
}

编辑:

在您的评论中,您说 Newspaper 需要 'Codable',并暗示服务器响应中没有 id 参数。下面的 NewspaperCodable,但不会期望服务器响应中出现 id,而只会添加自己的常量 id。计算 id 是一个非常糟糕的主意。 id 永远不应该改变,它应该是唯一的。 UUID 给你。

struct Newspaper: Identifiable, Comparable, Codable {
    let id = UUID()
    let name: String
    
    enum CodingKeys:String,CodingKey {
        case name
    }

    static func < (lhs: Newspaper, rhs: Newspaper) -> Bool {
        lhs.name < rhs.name
    }
}