无法让 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
参数。下面的 Newspaper
是 Codable
,但不会期望服务器响应中出现 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
}
}
我正在尝试列出最喜欢的报纸。在编辑模式下,列表显示所有可用的报纸,用户可以从中 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
参数。下面的 Newspaper
是 Codable
,但不会期望服务器响应中出现 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
}
}