根据未过滤的数组过滤 SwiftUI returns 中 ForEach 中的 @Binding 数组 var
Filtering a @Binding array var in a ForEach in SwiftUI returns values based on unfiltered array
我是一名 Windows C# 开发人员,iOS/SwiftUI 开发新手,我想我自己在这里遇到了麻烦。
我有一个带有@Binding 变量的视图:
struct DetailView: View {
@Binding var project: Project
该项目是一个包含一组任务的对象。我正在遍历项目的任务以显示其名称和一个切换,其状态由任务的变量 isComplete 确定。
ForEach(filteredTasks.indices, id: \.self) { idx in
HStack {
Text(filteredTasks[idx].phase)
.font(.caption)
Spacer()
Text(filteredTasks[idx].name)
Spacer()
Toggle("", isOn: self.$filteredTasks[idx].isComplete)
}
}
}
我花了很长时间才找到这段代码,我发现我必须按照一个带有 'indices' 选项的示例来让切换器单独处理每个任务,并且以确保其 isComplete 值已保存。
接下来,我想根据具有 Planning、Construction 或 Final 值的 Task 变量 phase 过滤任务列表。所以我创建了 4 个按钮(每个阶段一个,然后是一个 'All Tasks' 返回完整的、未过滤的列表),并且经过大量的反复试验(创建不再正确绑定的过滤数组,等等) ., 等)我试过了,基本上只使用原始数组。
List {
ForEach(project.tasks.filter({ [=13=].phase.contains(filterValue) }).indices, id: \.self) { idx in
HStack {
Text(project.tasks[idx].phase)
.font(.caption)
Spacer()
Text(project.tasks[idx].name)
Spacer()
Toggle("", isOn: self.$project.tasks[idx].isComplete)
}
}
}
当然,这似乎有效,因为我可以做一个测试:
func CreateTestArray() {
let testFilterArray = project.tasks.filter({ [=14=].phase.contains(filterValue) })
}
这将为我提供我想要的过滤列表。但是,在我的 ForEach 视图中,它无法正常工作,我不确定如何解决它。
例如,我有 128 个任务,其中 10 个的值为 'Final',当我使用按钮将 filterValue 设置为 Final 时,testFilterArray 实际上包含正确的 10 个任务 - 但在 ForEach查看我得到原始数组中的 前十个 任务(它们属于 'Planning' 类型 - 原始数组按 Planning/Construction/Final 排序);显然,尽管有过滤语句,ForEach 仍在处理原始数组。 Planning 按钮发送 filterValue = "Planning",我得到了正确的结果,因为我在原始数组中拥有的 20 个 Planning 任务的过滤器 returns 0-19 索引,并且因为它们是原始数组中的第一个数组,它 'appears' Planning 过滤器工作正常,但实际上它只是偶然工作,如果数组排序不同,它就不会。
有什么想法可以解决这个问题,以便我可以实际过滤这个数组,为数组中的每个项目正确显示 isComplete 切换,以及动态更新切换状态?我觉得我需要在这里再次从头开始,因为我让这些限制使我陷入了一个很小的 Swift 角落。
谢谢!
更新:
谢谢@jnpdx 的快速回复 - 我绝对应该包含对象(我在下面列出)。然而,回顾我的对象定义,我想知道我是否在管理对象时犯了一个更基本的错误,这就是为什么我陷入了我所遇到的情况(即,在早期的迭代中我尝试了一些你的建议)。无论如何,我发布的对象是 'projects',它是我传递给项目列表视图的项目列表,然后该视图将单个项目传递给项目视图,然后该视图列出其中的任务特定项目。
我觉得你的回答让我做出了正确的决定,我只需要后退并查看那些对象 definitions/management,看看如何达到可以直接解决的情况。
任务:
struct Task: Identifiable, Codable {
let id: UUID
var phase: String
var category: String
var name: String
var isComplete: Bool
init(id: UUID = UUID(), phase: String, category: String, name: String, isComplete: Bool) {
self.id = id
self.phase = phase
self.category = category
self.name = name
self.isComplete = isComplete
}
}
项目:
struct Project: Identifiable, Codable {
var id: UUID
var name: String
var type: String
var tasks: [Task]
var isComplete: Bool
init(id: UUID = UUID(), name: String, type: String, tasks: [Task] = [], isComplete: Bool) {
self.id = id
self.name = name
self.type = type
self.tasks = tasks
self.isComplete = isComplete
}
}
和项目模型:
class ProjectData: ObservableObject {
// code to access the json file is here
// An accessible list of projects from the saved file
@Published var projects: [Project] = []
// load and save functions follow
更新:
谢谢,@jnpdx,正如您所说,我需要进行调整以使其在我的特定模型设计中发挥作用,您的解决方案在进行了调整后才起作用。以下是最终在我的案例中起作用的片段。
在我看来:
List {
ForEach(project.tasks.filter({ [=18=].phase.contains(filterValue) })) { task in
HStack {
Text(task.name)
Toggle("", isOn: self.makeBinding(item: task))
}
}
}
调用函数:
func makeBinding(item: Task) -> Binding<Bool> {
let i = self.project.tasks.firstIndex { [=19=].id == item.id }!
return .init(
get: { self.project.tasks[i].isComplete },
set: { self.project.tasks[i].isComplete = [=19=] }
)
}
让我们看看您的代码中的以下行:
ForEach(project.tasks.filter({ [=10=].phase.contains(filterValue) }).indices, id: \.self) { idx in
在第一部分中,您过滤 tasks
然后请求索引。我怀疑您希望它 return 类似于 [1, 5, 10, 11, 12],这意味着它们在数组中的原始位置。但是,实际上,你会得到一个像 [0,1,2,3,4] 这样的连续数组,因为它为你提供了新创建的数组的索引(filter
的结果)。
有几种方法可以解决这个问题,这也与您之前的 ForEach 有关。
执行 ForEach
并遍历 structs/objects 而不是索引更为惯用。你没有显示 Task
是由什么组成的,但假设是这样的:
struct Task : Hashable {
var id = UUID()
var name: String
var phrase: String
var isComplete: Bool
}
要对其进行迭代,您可以这样做:
ForEach(task, id: \.id) { task in
Text(task.name)
Toggle("Done?", isOn: project.taskCompletedBinding(id: task.id)) //explained later
}
我在评论中询问了 Project
的类型,因为我不完全清楚为什么它是 @Binding。好像可能是一个对象?如果它是一个视图模型,那会很好,您可以在那里处理您的 Toggle
逻辑。类似于:
class Project : ObservableObject {
@Published var tasks : [Task] = [Task(name: "1", phrase: "phase", isComplete: false),Task(name: "2", phrase: "phase", isComplete: true),Task(name: "3", phrase: "phase2", isComplete: false)]
var completedTasks : [Task] {
return tasks.filter { [=13=].isComplete }
}
func taskCompletedBinding(id: UUID) -> Binding<Bool> {
Binding<Bool>(get: {
self.tasks.first(where: { [=13=].id == id})?.isComplete ?? false
}, set: { newValue in
self.tasks = self.tasks.map { t in
if t.id == id {
var tCopy = t
tCopy.isComplete = newValue
return tCopy
} else {
return t
}
}
})
}
}
您可以测试这样做是否有效:
struct ContentView: View {
@ObservedObject var project = Project()
var body: some View {
ForEach(project.tasks, id: \.id) { task in
Text(task.name)
Toggle("Done?", isOn: project.taskCompletedBinding(id: task.id))
}
}
}
如果 Project
是一个结构而不是一个对象,最好像我上面那样将它包装在 ObservableObject
视图模型中。
我是一名 Windows C# 开发人员,iOS/SwiftUI 开发新手,我想我自己在这里遇到了麻烦。
我有一个带有@Binding 变量的视图:
struct DetailView: View {
@Binding var project: Project
该项目是一个包含一组任务的对象。我正在遍历项目的任务以显示其名称和一个切换,其状态由任务的变量 isComplete 确定。
ForEach(filteredTasks.indices, id: \.self) { idx in
HStack {
Text(filteredTasks[idx].phase)
.font(.caption)
Spacer()
Text(filteredTasks[idx].name)
Spacer()
Toggle("", isOn: self.$filteredTasks[idx].isComplete)
}
}
}
我花了很长时间才找到这段代码,我发现我必须按照一个带有 'indices' 选项的示例来让切换器单独处理每个任务,并且以确保其 isComplete 值已保存。
接下来,我想根据具有 Planning、Construction 或 Final 值的 Task 变量 phase 过滤任务列表。所以我创建了 4 个按钮(每个阶段一个,然后是一个 'All Tasks' 返回完整的、未过滤的列表),并且经过大量的反复试验(创建不再正确绑定的过滤数组,等等) ., 等)我试过了,基本上只使用原始数组。
List {
ForEach(project.tasks.filter({ [=13=].phase.contains(filterValue) }).indices, id: \.self) { idx in
HStack {
Text(project.tasks[idx].phase)
.font(.caption)
Spacer()
Text(project.tasks[idx].name)
Spacer()
Toggle("", isOn: self.$project.tasks[idx].isComplete)
}
}
}
当然,这似乎有效,因为我可以做一个测试:
func CreateTestArray() {
let testFilterArray = project.tasks.filter({ [=14=].phase.contains(filterValue) })
}
这将为我提供我想要的过滤列表。但是,在我的 ForEach 视图中,它无法正常工作,我不确定如何解决它。
例如,我有 128 个任务,其中 10 个的值为 'Final',当我使用按钮将 filterValue 设置为 Final 时,testFilterArray 实际上包含正确的 10 个任务 - 但在 ForEach查看我得到原始数组中的 前十个 任务(它们属于 'Planning' 类型 - 原始数组按 Planning/Construction/Final 排序);显然,尽管有过滤语句,ForEach 仍在处理原始数组。 Planning 按钮发送 filterValue = "Planning",我得到了正确的结果,因为我在原始数组中拥有的 20 个 Planning 任务的过滤器 returns 0-19 索引,并且因为它们是原始数组中的第一个数组,它 'appears' Planning 过滤器工作正常,但实际上它只是偶然工作,如果数组排序不同,它就不会。
有什么想法可以解决这个问题,以便我可以实际过滤这个数组,为数组中的每个项目正确显示 isComplete 切换,以及动态更新切换状态?我觉得我需要在这里再次从头开始,因为我让这些限制使我陷入了一个很小的 Swift 角落。
谢谢!
更新: 谢谢@jnpdx 的快速回复 - 我绝对应该包含对象(我在下面列出)。然而,回顾我的对象定义,我想知道我是否在管理对象时犯了一个更基本的错误,这就是为什么我陷入了我所遇到的情况(即,在早期的迭代中我尝试了一些你的建议)。无论如何,我发布的对象是 'projects',它是我传递给项目列表视图的项目列表,然后该视图将单个项目传递给项目视图,然后该视图列出其中的任务特定项目。
我觉得你的回答让我做出了正确的决定,我只需要后退并查看那些对象 definitions/management,看看如何达到可以直接解决的情况。
任务:
struct Task: Identifiable, Codable {
let id: UUID
var phase: String
var category: String
var name: String
var isComplete: Bool
init(id: UUID = UUID(), phase: String, category: String, name: String, isComplete: Bool) {
self.id = id
self.phase = phase
self.category = category
self.name = name
self.isComplete = isComplete
}
}
项目:
struct Project: Identifiable, Codable {
var id: UUID
var name: String
var type: String
var tasks: [Task]
var isComplete: Bool
init(id: UUID = UUID(), name: String, type: String, tasks: [Task] = [], isComplete: Bool) {
self.id = id
self.name = name
self.type = type
self.tasks = tasks
self.isComplete = isComplete
}
}
和项目模型:
class ProjectData: ObservableObject {
// code to access the json file is here
// An accessible list of projects from the saved file
@Published var projects: [Project] = []
// load and save functions follow
更新: 谢谢,@jnpdx,正如您所说,我需要进行调整以使其在我的特定模型设计中发挥作用,您的解决方案在进行了调整后才起作用。以下是最终在我的案例中起作用的片段。
在我看来:
List {
ForEach(project.tasks.filter({ [=18=].phase.contains(filterValue) })) { task in
HStack {
Text(task.name)
Toggle("", isOn: self.makeBinding(item: task))
}
}
}
调用函数:
func makeBinding(item: Task) -> Binding<Bool> {
let i = self.project.tasks.firstIndex { [=19=].id == item.id }!
return .init(
get: { self.project.tasks[i].isComplete },
set: { self.project.tasks[i].isComplete = [=19=] }
)
}
让我们看看您的代码中的以下行:
ForEach(project.tasks.filter({ [=10=].phase.contains(filterValue) }).indices, id: \.self) { idx in
在第一部分中,您过滤 tasks
然后请求索引。我怀疑您希望它 return 类似于 [1, 5, 10, 11, 12],这意味着它们在数组中的原始位置。但是,实际上,你会得到一个像 [0,1,2,3,4] 这样的连续数组,因为它为你提供了新创建的数组的索引(filter
的结果)。
有几种方法可以解决这个问题,这也与您之前的 ForEach 有关。
执行 ForEach
并遍历 structs/objects 而不是索引更为惯用。你没有显示 Task
是由什么组成的,但假设是这样的:
struct Task : Hashable {
var id = UUID()
var name: String
var phrase: String
var isComplete: Bool
}
要对其进行迭代,您可以这样做:
ForEach(task, id: \.id) { task in
Text(task.name)
Toggle("Done?", isOn: project.taskCompletedBinding(id: task.id)) //explained later
}
我在评论中询问了 Project
的类型,因为我不完全清楚为什么它是 @Binding。好像可能是一个对象?如果它是一个视图模型,那会很好,您可以在那里处理您的 Toggle
逻辑。类似于:
class Project : ObservableObject {
@Published var tasks : [Task] = [Task(name: "1", phrase: "phase", isComplete: false),Task(name: "2", phrase: "phase", isComplete: true),Task(name: "3", phrase: "phase2", isComplete: false)]
var completedTasks : [Task] {
return tasks.filter { [=13=].isComplete }
}
func taskCompletedBinding(id: UUID) -> Binding<Bool> {
Binding<Bool>(get: {
self.tasks.first(where: { [=13=].id == id})?.isComplete ?? false
}, set: { newValue in
self.tasks = self.tasks.map { t in
if t.id == id {
var tCopy = t
tCopy.isComplete = newValue
return tCopy
} else {
return t
}
}
})
}
}
您可以测试这样做是否有效:
struct ContentView: View {
@ObservedObject var project = Project()
var body: some View {
ForEach(project.tasks, id: \.id) { task in
Text(task.name)
Toggle("Done?", isOn: project.taskCompletedBinding(id: task.id))
}
}
}
如果 Project
是一个结构而不是一个对象,最好像我上面那样将它包装在 ObservableObject
视图模型中。