sid*_*sid 6 arrays filter indices swiftui
我是一名 Windows C# 开发人员,刚接触 iOS/SwiftUI 开发,我想我已经陷入了困境。
我有一个带有 @Binding 变量的视图:
struct DetailView: View {
@Binding var project: Project
Run Code Online (Sandbox Code Playgroud)
该项目是一个包含任务数组的对象。我循环遍历项目的任务以显示其名称和一个开关,其状态由任务的变量 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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
我花了相当长的时间才完成这段代码,我发现我必须遵循带有“索引”选项的示例才能使切换单独处理每个任务,并确保其 isComplete 值被救了。
接下来,我想根据任务变量、阶段(其值为 Planning、Construction 或 Final)过滤任务列表。因此,我创建了 4 个按钮(每个阶段一个,然后是“所有任务”以返回完整的、未过滤的列表),经过大量的试验和错误(创建不再正确绑定的过滤数组等)。等)我尝试过这个,基本上只使用原始数组。
List {
ForEach(project.tasks.filter({ $0.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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
当然,这似乎有效,因为我可以做一个测试:
func CreateTestArray() {
let testFilterArray = project.tasks.filter({ $0.phase.contains(filterValue) })
}
Run Code Online (Sandbox Code Playgroud)
这将为我提供我想要的过滤列表。但是,在我的 ForEach 视图中,它无法正常工作,并且我不确定如何解决它。
例如,我有 128 个任务,其中 10 个任务的值为“Final”,当我使用按钮将 filterValue 设置为 Final 时,testFilterArray 实际上包含正确的 10 个任务 - 但在 ForEach 视图中我得到的是第一个任务原始数组中的十个任务(其类型为“规划” - 原始数组按规划/施工/最终排序);显然,尽管有过滤器语句,ForEach 仍在原始数组上工作。Planning 按钮发送 filterValue =“Planning”,我得到了正确的结果,因为过滤器为原始数组中的 20 个规划任务返回 0-19 个索引,并且由于它们位于原始数组中的第一个,因此“看起来“规划过滤器工作正常”,但实际上它只是偶然工作,如果数组以不同的方式排序,它就不会工作。
有什么想法可以解决这个问题,以便我可以实际过滤该数组,为数组中的每个项目正确显示 isComplete 切换,以及动态更新切换状态?我觉得我需要再次从头开始,因为我已经让这些限制让我陷入了一个小小的 Swift 角落。
谢谢!
更新:谢谢@jnpdx,您的快速回复 - 我绝对应该包含对象(我在下面列出)。然而,回顾我的对象定义,我想知道我在管理对象时是否犯了一个更基本的错误,这就是为什么我陷入了我所遇到的情况(即,在早期的迭代中我尝试了一些您的建议)。无论如何,我发布的对象是“项目”,它是我传递到项目列表视图的项目列表,然后该视图将单个项目传递到项目视图,然后该视图列出该特定项目中的任务。
我觉得你的答案为我指明了正确的决定,我只需要备份并查看那些对象定义/管理,看看如何达到可以提供简单解决方案的情况。
任务:
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
}
}
Run Code Online (Sandbox Code Playgroud)
该项目:
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
}
}
Run Code Online (Sandbox Code Playgroud)
和项目模型:
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
Run Code Online (Sandbox Code Playgroud)
更新:谢谢,@jnpdx,正如您所说,我需要进行调整以使其在我的特定模型设计中发挥作用,您的解决方案在进行调整后就起作用了。以下是最终在我的案例中起作用的片段。
在我看来:
List {
ForEach(project.tasks.filter({ $0.phase.contains(filterValue) })) { task in
HStack {
Text(task.name)
Toggle("", isOn: self.makeBinding(item: task))
}
}
}
Run Code Online (Sandbox Code Playgroud)
以及被调用的函数:
func makeBinding(item: Task) -> Binding<Bool> {
let i = self.project.tasks.firstIndex { $0.id == item.id }!
return .init(
get: { self.project.tasks[i].isComplete },
set: { self.project.tasks[i].isComplete = $0 }
)
}
Run Code Online (Sandbox Code Playgroud)
让我们看看代码中的以下行:
ForEach(project.tasks.filter({ $0.phase.contains(filterValue) }).indices, id: \.self) { idx in
Run Code Online (Sandbox Code Playgroud)
在第一部分中,您进行过滤tasks,然后询问索引。我怀疑您希望它返回类似 [1, 5, 10, 11, 12] 的内容,这意味着它们在数组中的原始位置。但是,实际上,您将获得一个像 [0,1,2,3,4] 这样的连续数组,因为它为您提供新创建的数组的索引( 的结果filter)。
有几种方法可以解决这个问题,这些方法也与您之前使用的 ForEach 相关。
ForEach迭代结构/对象比索引更惯用。你没有显示它Task是由什么组成的,但我们假设它是这样的:
struct Task : Hashable {
var id = UUID()
var name: String
var phrase: String
var isComplete: Bool
}
Run Code Online (Sandbox Code Playgroud)
要迭代它,你可以这样做:
ForEach(task, id: \.id) { task in
Text(task.name)
Toggle("Done?", isOn: project.taskCompletedBinding(id: task.id)) //explained later
}
Run Code Online (Sandbox Code Playgroud)
我在评论中询问了 的类型,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 { $0.isComplete }
}
func taskCompletedBinding(id: UUID) -> Binding<Bool> {
Binding<Bool>(get: {
self.tasks.first(where: { $0.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
}
}
})
}
}
Run Code Online (Sandbox Code Playgroud)
您可以通过以下方式测试它是否有效:
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))
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果Project是一个结构体而不是一个对象,那么将它包装在视图模型中可能会很好,ObservableObject就像我上面所做的那样。
| 归档时间: |
|
| 查看次数: |
2632 次 |
| 最近记录: |