我正在开发 SwiftUI 应用程序,并遇到了一个与
Binding
内的 ForEach
循环中的 List
相关的有趣问题。我的困惑在于绑定传递给子视图的方式。
这是我的
HabitListView
的工作版本,我使用 ForEach
来迭代 habitStore.habits
。但是,我注意到当将 $
传递给 habit
和 HabitRow
时,不使用绑定符号 EditHabitView
,即使两者都期望绑定:
struct HabitListView: View {
@EnvironmentObject var habitStore: HabitStore
var body: some View {
NavigationStack {
List {
ForEach($habitStore.habits) { habit in
NavigationLink(destination: EditHabitView(habit: habit)) {
HabitRow(habit: habit)
}
}
}
}
}
}
有趣的是,如果我修改代码以在闭包和子视图中显式包含
$
符号,则程序编译并运行时不会出现任何问题:
struct HabitListView: View {
@EnvironmentObject var habitStore: HabitStore
var body: some View {
NavigationStack {
List {
ForEach($habitStore.habits) { $habit in
NavigationLink(destination: EditHabitView(habit: $habit)) {
HabitRow(habit: $habit)
}
}
}
}
}
}
尝试混合使用绑定符号会导致编译时错误:
混合01:
struct HabitListView: View {
@EnvironmentObject var habitStore: HabitStore
var body: some View {
NavigationStack {
List {
ForEach($habitStore.habits) { $habit in
NavigationLink(destination: EditHabitView(habit: habit)) {
HabitRow(habit: habit)
}
}
}
}
}
}
混合02:
struct HabitListView: View {
@EnvironmentObject var habitStore: HabitStore
var body: some View {
NavigationStack {
List {
ForEach($habitStore.habits) { habit in
NavigationLink(destination: EditHabitView(habit: $habit)) {
HabitRow(habit: $habit)
}
}
}
}
}
}
struct EditHabitView: View {
@EnvironmentObject var habitStore: HabitStore
@State private var draftHabit: Habit
@Environment(\.dismiss) var dismiss
init(habit: Binding<Habit>) {
_draftHabit = State(initialValue: habit.wrappedValue)
}
var body: some View {
Form {
TextField("Habit name", text: $draftHabit.name)
TextField("Habit description", text: $draftHabit.description)
Picker("Select unit", selection: $draftHabit.unit) {
ForEach(["hours", "reps", "kg", "liters"], id: \.self) { unit in
Text(unit)
}
}
.pickerStyle(.wheel)
Stepper("Counting \(draftHabit.count)", value: $draftHabit.count)
}
}
}
struct HabitRow: View {
@Binding var habit: Habit
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(habit.name).font(.headline)
Text(habit.description).font(.subheadline)
}
Spacer()
Text("\(habit.count) \(habit.unit)")
Stepper("", value: $habit.count).labelsHidden()
}
}
}
为什么初始代码片段编译成功,尽管
HabitRow
和 EditHabitView
应该需要 Binding<Habit>
类型,但 habit
却在不使用 $
的情况下传递了?此外,为什么显式使用 $
也可以毫无问题地工作,以及是什么导致了混合场景中的错误?
初始片段有效,因为当您传递 ForEach($habitStore.habits) 时,ForEach 循环中的习惯隐式是一个 Binding
。循环变量中的显式 $ 对于清晰起见很有用,但语言不需要。错误地混合使用会引入语法和类型错误,因为如果没有明确的指示,Swift 无法协调混合的意图。 通过指定 $habit,您实际上是在要求 Swift 同时将习惯视为原始习惯和绑定,这会导致混乱和错误,因为在闭包参数中显式使用绑定符号 ($),而不是在闭包代码中使用符号会使编译器认为您想要使用参数的非绑定版本。并且执行相反的操作(不在参数中使用符号,而是在代码中使用)使您无法访问参数的两个版本。
这看起来很合乎逻辑,但我找不到任何来源支持这一点。
有人可以确认或更正吗?
传递给
ForEach
content
闭包的参数类型由 ForEach
正在操作的集合的类型决定。
就您而言,您的收藏是
$habitStore.habits
,因此您的闭包将收到 Binding<Habit>
。这意味着,在您的第一个代码块中, habit
是 Binding<Habit>
并且可以将其传递给 EditHabitView
。
在第二个代码块中,您只需将
ForEach
中的变量从 habit
重命名为 $habit
- $
不会更改变量的类型。无论哪种方式都是Binding<Habit>
。
在“混合”代码块中,您会收到错误,因为您已将闭包参数声明为
$habit
,但随后在第二个“混合”代码块中引用了未定义的变量 habit
,反之亦然。