提问人:Boris Ryabov 提问时间:7/15/2023 最后编辑:burnsiBoris Ryabov 更新时间:7/23/2023 访问量:58
如何使 Picker onChange 更新选定的结构?
How to make Picker onChange update a selected struct?
问:
我有两个结构。其中一个结构有一个字段,用于存储另一个结构的实例。反过来,具有与之关联的结构的 ID 列表。Idea
Option
Option
Idea
我创建了一个 Picker,它将这个想法的 id 添加到 Option 中的关联想法列表中。Idea.Option
这会引发此错误。
代码如下
//ContentView
import SwiftUI
struct ContentView: View {
var options: [Option] = [
Option(name: "option1", assositatedIdea: []),
Option(name: "option2", assositatedIdea: []),
Option(name: "option3", assositatedIdea: [])
]
@Binding var ideas: Idea
var body: some View {
Picker("Option: \(ideas.option.name)", selection: $ideas.option) {
ForEach(options) {option in
Text(option.name).tag(option)
}
}.onChange(of: ideas.option) {newOption in
ideas.option.addAssosiatedIdea(ideaId: ideas.id)
}
}
}
struct Option: Hashable, Identifiable, Equatable {
let id: UUID
var name: String
var assositatedIdea: [UUID] // Stores IDs of structs of Ideas
init(id: UUID = UUID(), name: String, assositatedIdea: [UUID]) {
self.id = id
self.name = name
self.assositatedIdea = assositatedIdea
}
mutating func addAssosiatedIdea(ideaId: UUID) {
if (assositatedIdea.contains(ideaId)) {
return
}
self.assositatedIdea.append(ideaId)
}
}
extension Option {
static var emptyOption: Option {
Option(name: "", assositatedIdea: [])
}
}
struct Idea {
let id: UUID
var name: String
var option: Option
init(id: UUID = UUID(), name: String, option: Option) {
self.id = id
self.name = name
self.option = option
}
}
// test1App.swift
import SwiftUI
@main
struct test1App: App {
@State var ideas: Idea = Idea(name: "big brain idea", option: Option.emptyOption)
var body: some Scene {
WindowGroup {
ContentView(ideas: $ideas)
}
}
}
我希望 Picker 预览新选项。但是,Picker 会引用并显示 中的第一个选项。options
我做了一些打印调试,发现实际上 Idea 更新了其字段。
答:
0赞
Benzy Neez
7/15/2023
#1
解释
首先,这是对错误的解释。
- 结构的 id 类型为 。
Option
UUID
- 因此,每个人都有一个唯一但不可预测的 ID。
Option
- 您正在使用 的数组创建 ,该数组是视图的本地数组。
Picker
Option
ContentView
- 您正在将 绑定到 。
Picker
$ideas.option
- 绑定值不会与数组中的任何值匹配,因为它肯定有自己的唯一 ID。
Option
- 这就是为什么给出错误“选择 [Option] 无效且没有关联的标签”的原因。
Picker
两个可能不匹配的另一个原因是,每个都有一个数组。这些是关联的 .Option
Option
UUID
Idea
快速修复
- 解决这些问题的快速方法是确保两个本应相同的选项实际上相等。因此,您可以使用简单的 .为了忽略关联的 id,您还应该实现自己的相等函数:
UUID
Int
struct Option: Hashable, Identifiable, Equatable {
let id: Int // <- not UUID
var name: String
var assositatedIdea: [UUID] // Stores IDs of structs of Ideas
init(id: Int, name: String, assositatedIdea: [UUID]) {
self.id = id
self.name = name
self.assositatedIdea = assositatedIdea
}
static func ==(lhs: Option, rhs: Option) -> Bool {
lhs.id == rhs.id
}
- 空选项的 id 可以为 0:
static var emptyOption: Option {
Option(id: 0, name: "", assositatedIdea: [])
}
- 即使进行了这些更改,选取器仍会抱怨绑定持有的选项与任何标记都不匹配,因为你使用的是空选项进行创建。因此,在可选择选项列表中包含一个空选项:
ideas
var options: [Option] = [
Option(id: 0, name: "empty", assositatedIdea: []), // <- ADDED
Option(id: 1, name: "option1", assositatedIdea: []),
Option(id: 2, name: "option2", assositatedIdea: []),
Option(id: 3, name: "option3", assositatedIdea: [])
]
替代模型
上面的更改使它正常工作,但它仍然有点混乱。特别是,保存 ID 集合的方式意味着这两个结构是相互依赖的。Option
Idea
以下是一些关于如何使数据模型和实现更简洁的建议:
- 如果可能,使用枚举来定义可能的选项(如果选项是固定的并且事先知道,则有效)。
- 尽可能使用代替。
let
var
- 与其让选项知道引用它们的想法,不如使用另一种新类型来模拟这种关系。
- 与其收集想法的 ID (UUID),不如收集实际的想法。
Idea
可能根本不需要 ID。
下面说明了如何以这种方式实现它。对 和 关联之间的关系进行建模的新类型是 ,它是一种类类型。该类是这些类的映射的包装器。Option
Ideas
OptionAndIdeas
AllOptionsAndIdeas
enum Option: Int, Identifiable {
case empty = 0
case option1 = 1
case option2 = 2
case option3 = 3
var id: Int {
self.rawValue
}
var asString: String {
"\(self)"
}
}
struct Idea: Equatable {
let option: Option
let name: String
}
class OptionAndIdeas {
let option: Option
var associatedIdeas: [Idea]
init(option: Option, associatedIdeas: [Idea] = []) {
self.option = option
self.associatedIdeas = associatedIdeas
}
func addAssociatedIdea(idea: Idea) {
if !associatedIdeas.contains(idea) {
associatedIdeas.append(idea)
}
}
func removeAssociatedIdea(idea: Idea) {
if let index = associatedIdeas.firstIndex(of: idea) {
associatedIdeas.remove(at: index)
}
}
}
class AllOptionAndIdeas {
/// A map of OptionAndIdeas, keyed by option
private var allOptionsAndIdeas = [Option: OptionAndIdeas]()
func switchIdeaToOption(idea: Idea, option: Option) -> Idea {
// Remove the idea from its previous association
if let optionAndIdeas = allOptionsAndIdeas[idea.option] {
optionAndIdeas.removeAssociatedIdea(idea: idea)
}
// Re-create the idea, associating it with the new option
let result = Idea(option: option, name: idea.name)
// Update the associations between options and ideas
let optionAndIdeas = allOptionsAndIdeas[option] ?? OptionAndIdeas(option: option)
optionAndIdeas.addAssociatedIdea(idea: result)
allOptionsAndIdeas[option] = optionAndIdeas
return result
}
}
struct PickerExample: View {
private let options: [Option] = [ .empty, .option1, .option2, .option3 ]
@Binding private var idea: Idea
private let allOptionsAndIdeas: AllOptionAndIdeas
@State private var selectedOption: Option
init(idea: Binding<Idea>, allOptionsAndIdeas: AllOptionAndIdeas) {
self._idea = idea
self.allOptionsAndIdeas = allOptionsAndIdeas
self._selectedOption = State(initialValue: idea.wrappedValue.option)
}
var body: some View {
Picker("Option", selection: $selectedOption) {
ForEach(options) { option in
Text(option.asString).tag(option)
}
}
.onChange(of: selectedOption) { newOption in
// Switch to the selected option
idea = allOptionsAndIdeas.switchIdeaToOption(idea: idea, option: newOption)
}
}
}
struct ContentView: View {
private let allOptionsAndIdeas = AllOptionAndIdeas()
@State private var bigBrainIdea = Idea(option: .empty, name: "big brain idea")
var body: some View {
PickerExample(idea: $bigBrainIdea, allOptionsAndIdeas: allOptionsAndIdeas)
}
}
评论
0赞
Boris Ryabov
7/16/2023
由于在我的项目中,两者都是用户制作的,因此我决定将这两者绑定为可选的数据结构。绑定将在 App init 上进行。谢谢你的提示!Idea
Option
评论