SwiftUI:绑定到从环境 object 派生的结构的 属性
SwiftUI: Binding on property of struct derived from environment object
我的任务管理器应用程序中有两个结构:
struct Task {
var identifier: String
var title: String
var tags: [String] // Array of tag identifiers
}
struct Tag {
var identifier: String
var title: String
}
然后我有一个 class 来存储它们:
class TaskStore: ObservableObject {
@Published var tasks = [String:Task]()
@Published var tags = [String:Tag]()
}
我将其作为 .environmentObject(taskStore)
.
传递给我的根视图
如果以下任何错误(反对不良做法),请纠正我:
在我的 TaskView
我有:
@EnvironmentObject var taskStore: TaskStore
var taskIdentifier: String // Passed from parent view
private var task: Task {
get {
return taskStore.tasks[taskIdentifier]! // Looks up the task in the store
}
}
private var tags: [Tag] {
get {
return taskStore.tags
}
}
问题是,在学习 SwiftUI 时,我被告知在制作某些组件时(比如在这种情况下让你改变标签数组的选择器)它应该接受对 value/collection 的绑定,或者说我想让任务标题可编辑,我需要绑定到 task.title
属性,这两点我都做不到,因为(基于我定义和计算的方式 task
) 我无法绑定 task
.
我是不是在做一些违反最佳实践的事情?或者在这条道路上,我在哪里偏离了在环境中存储真实点的正确方法object,并使它们在子视图中可编辑。
不,您不一定在做违反最佳实践的事情。我认为在 SwiftUI 中,数据模型存储和操作的概念很快变得比 Apple 倾向于在其演示代码中展示的内容更加复杂。对于真正的应用程序,具有单一事实来源,就像您似乎正在使用的那样,您将不得不想出一些方法将数据绑定到您的视图。
一种解决方案是使用您自己的 get
和 set
属性编写 Binding
,这些属性与您的 ObservableObject
交互。这可能看起来像这样,例如:
struct TaskView : View {
var taskIdentifier: String // Passed from parent view
@EnvironmentObject private var taskStore: TaskStore
private var taskBinding : Binding<Task> {
Binding {
taskStore.tasks[taskIdentifier] ?? .init(identifier: "", title: "", tags: [])
} set: {
taskStore.tasks[taskIdentifier] = [=10=]
}
}
var body: some View {
TextField("Task title", text: taskBinding.title)
}
}
如果您不喜欢这种事情,避免这种情况的一种方法是使用 CoreData。因为模型是由系统做成ObservableObject
的,所以你一般可以避免这种事情,直接传递和操作你的模型。然而,这并不一定意味着它是正确(或更好)的选择。
您可能还想探索 TCA,这是一个越来越受欢迎的状态管理和视图绑定库,它为您正在做的事情提供了很多 built-in 解决方案。
您使用值类型对数据建模并管理生命周期,side-effects 使用引用类型是正确的。您缺少的一点是 Task 没有实现 Identifiable
协议,该协议使 SwiftUI 能够跟踪 List
或 ForEach
中的数据。按如下方式实施:
struct Task: Identifiable {
var id: String
var title: String
var tags: [String] // Array of tag identifiers
}
然后切换到使用数组,例如
class TaskStore: ObservableObject {
@Published var tasks = [Task]()
@Published var tags = [Tag]()
// you might find this helper found in Fruta useful
func task(for identifier: String) -> Task? {
return tasks.first(where: { [=11=].id == identifier })
}
}
现在您已经有了一组可识别的数据,通过以下方式绑定到任务真的很简单:
List($model.tasks) { $task in
// now you have a binding to the task
}
我建议查看 Apple's Fruta sample 了解更多详情。
我的任务管理器应用程序中有两个结构:
struct Task {
var identifier: String
var title: String
var tags: [String] // Array of tag identifiers
}
struct Tag {
var identifier: String
var title: String
}
然后我有一个 class 来存储它们:
class TaskStore: ObservableObject {
@Published var tasks = [String:Task]()
@Published var tags = [String:Tag]()
}
我将其作为 .environmentObject(taskStore)
.
如果以下任何错误(反对不良做法),请纠正我:
在我的 TaskView
我有:
@EnvironmentObject var taskStore: TaskStore
var taskIdentifier: String // Passed from parent view
private var task: Task {
get {
return taskStore.tasks[taskIdentifier]! // Looks up the task in the store
}
}
private var tags: [Tag] {
get {
return taskStore.tags
}
}
问题是,在学习 SwiftUI 时,我被告知在制作某些组件时(比如在这种情况下让你改变标签数组的选择器)它应该接受对 value/collection 的绑定,或者说我想让任务标题可编辑,我需要绑定到 task.title
属性,这两点我都做不到,因为(基于我定义和计算的方式 task
) 我无法绑定 task
.
我是不是在做一些违反最佳实践的事情?或者在这条道路上,我在哪里偏离了在环境中存储真实点的正确方法object,并使它们在子视图中可编辑。
不,您不一定在做违反最佳实践的事情。我认为在 SwiftUI 中,数据模型存储和操作的概念很快变得比 Apple 倾向于在其演示代码中展示的内容更加复杂。对于真正的应用程序,具有单一事实来源,就像您似乎正在使用的那样,您将不得不想出一些方法将数据绑定到您的视图。
一种解决方案是使用您自己的 get
和 set
属性编写 Binding
,这些属性与您的 ObservableObject
交互。这可能看起来像这样,例如:
struct TaskView : View {
var taskIdentifier: String // Passed from parent view
@EnvironmentObject private var taskStore: TaskStore
private var taskBinding : Binding<Task> {
Binding {
taskStore.tasks[taskIdentifier] ?? .init(identifier: "", title: "", tags: [])
} set: {
taskStore.tasks[taskIdentifier] = [=10=]
}
}
var body: some View {
TextField("Task title", text: taskBinding.title)
}
}
如果您不喜欢这种事情,避免这种情况的一种方法是使用 CoreData。因为模型是由系统做成ObservableObject
的,所以你一般可以避免这种事情,直接传递和操作你的模型。然而,这并不一定意味着它是正确(或更好)的选择。
您可能还想探索 TCA,这是一个越来越受欢迎的状态管理和视图绑定库,它为您正在做的事情提供了很多 built-in 解决方案。
您使用值类型对数据建模并管理生命周期,side-effects 使用引用类型是正确的。您缺少的一点是 Task 没有实现 Identifiable
协议,该协议使 SwiftUI 能够跟踪 List
或 ForEach
中的数据。按如下方式实施:
struct Task: Identifiable {
var id: String
var title: String
var tags: [String] // Array of tag identifiers
}
然后切换到使用数组,例如
class TaskStore: ObservableObject {
@Published var tasks = [Task]()
@Published var tags = [Tag]()
// you might find this helper found in Fruta useful
func task(for identifier: String) -> Task? {
return tasks.first(where: { [=11=].id == identifier })
}
}
现在您已经有了一组可识别的数据,通过以下方式绑定到任务真的很简单:
List($model.tasks) { $task in
// now you have a binding to the task
}
我建议查看 Apple's Fruta sample 了解更多详情。