'SwiftUI: Binding on property of struct derived from environment object
I have two structs in my task manager app:
struct Task {
var identifier: String
var title: String
var tags: [String] // Array of tag identifiers
}
struct Tag {
var identifier: String
var title: String
}
I then have a class to store them:
class TaskStore: ObservableObject {
@Published var tasks = [String:Task]()
@Published var tags = [String:Tag]()
}
which I pass to my root view as an .environmentObject(taskStore).
Correct me if any of the following is wrong (against bad practices):
In my TaskView I have:
@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
}
}
The issue is, when learning SwiftUI I was told when making certain components (like a picker that let's you alter the tags array in this case) that it should accept a binding to the value/collection, or say I want to make the task title editable, I need a binding to the task.title property, both of which I can't do because (based on the way I'm defining and computing task) I can't get a binding on task.
Am I doing something against best practices here? Or where along this path did I diverge from the right way of storing points of truth in an environment object, and make them editable in sub views.
Solution 1:[1]
You are correct to model your data with value types and manage lifecycle and side-effects with a reference type. The bit you are missing is that Task doesn't implement the Identifiable protocol which enables SwiftUI to keep track of the data in a List or ForEach. Implement that as follows:
struct Task: Identifiable {
var id: String
var title: String
var tags: [String] // Array of tag identifiers
}
Then switch to using an array, e.g.
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: { $0.id == identifier })
}
}
Now that you have an array of identifiable data it is real simple to get a binding to the task via:
List($model.tasks) { $task in
// now you have a binding to the task
}
I recommend checking out Apple's Fruta sample for more detail.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
