1

我的任务管理器应用程序中有两个结构:

struct Task {
    var identifier: String
    var title: String
    var tags: [String] // Array of tag identifiers
}
struct Tag {
    var identifier: String
    var title: String
}

然后我有一个类来存储它们:

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 时,我被告知在制作某些组件时(比如在这种情况下让您更改标签数组的选择器)它应该接受对值/集合的绑定,或者说我想制作任务标题可编辑,我需要绑定到task.title属性,这两个我都做不到,因为(基于我定义和计算的方式task)我无法在task.

我在这里做一些违反最佳实践的事情吗?或者沿着这条路径,我在哪里偏离了在环境对象中存储真实点的正确方式,并使它们在子视图中可编辑。

4

2 回答 2

1

使用值类型对数据建模并使用引用类型管理生命周期和副作用是正确的。您缺少的一点是 Task 没有实现Identifiable使 SwiftUI 能够跟踪 aList或中的数据的协议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: { $0.id == identifier })
    }
}

现在您有了一组可识别的数据,通过以下方式获得与任务的绑定非常简单:

List($model.tasks) { $task in 
    // now you have a binding to the task
}

我建议查看Apple 的 Fruta 示例以获取更多详细信息。

于 2022-02-06T12:20:43.110 回答
1

不,您不一定会违反最佳实践。我认为在 SwiftUI 中,数据模型存储和操作的概念很快变得比 Apple 在其演示代码中倾向于展示的概念更加复杂。对于一个真实的应用程序,具有单一事实来源,就像您似乎正在使用的那样,您将不得不想出一些方法来将数据绑定到您的视图。

一种解决方案是Binding使用您自己的 sgetset与您的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] = $0
        }
    }
    
    var body: some View {
        TextField("Task title", text: taskBinding.title)
    }
}

如果你不喜欢这种事情,避免它的一种方法是使用 CoreData。因为模型是系统做成ObservableObject的,所以一般可以避免这种事情,直接传递和操作你的模型。但是,这并不一定意味着它也是正确(或更好)的选择。

您可能还想探索TCA,它是一个越来越流行的状态管理和视图绑定库,它为您正在寻找的事情类型提供了相当多的内置解决方案。

于 2022-02-06T03:23:19.413 回答