关于在 SwiftUI 列表中使用 ForEach 进行绑定的困惑

问题描述 投票:0回答:1

问题描述

我正在开发 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)
                    }
                }
            }
        }
    }
}

混合绑定符号时的错误

尝试混合使用绑定符号会导致编译时错误:

  1. 混合01:

    • 错误:无法将“Binding<[Habit]>”类型的值转换为预期的参数类型“Range
    • 错误:推断的投影类型“Int”不是属性包装器
    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)
                        }
                    }
                }
            }
        }
    }
    
  2. 混合02:

    • 错误:在范围内找不到“$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)
                        }
                    }
                }
            }
        }
    }
    

完整视图代码

编辑习惯视图

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
却在不使用
$
的情况下传递了?此外,为什么显式使用
$
也可以毫无问题地工作,以及是什么导致了混合场景中的错误?

GPT 答案

初始片段有效,因为当您传递 ForEach($habitStore.habits) 时,ForEach 循环中的习惯隐式是一个 Binding。循环变量中的显式 $ 对于清晰起见很有用,但语言不需要。错误地混合使用会引入语法和类型错误,因为如果没有明确的指示,Swift 无法协调混合的意图。

通过指定 $habit,您实际上是在要求 Swift 同时将习惯视为原始习惯和绑定,这会导致混乱和错误,因为在闭包参数中显式使用绑定符号 ($),而不是在闭包代码中使用符号会使编译器认为您想要使用参数的非绑定版本。并且执行相反的操作(不在参数中使用符号,而是在代码中使用)使您无法访问参数的两个版本。

这看起来很合乎逻辑,但我找不到任何来源支持这一点。

有人可以确认或更正吗?

swift swiftui binding swiftui-foreach
1个回答
0
投票

传递给

ForEach
content
闭包的参数类型由
ForEach
正在操作的集合的类型决定。

就您而言,您的收藏是

$habitStore.habits
,因此您的闭包将收到
Binding<Habit>
。这意味着,在您的第一个代码块中,
habit
Binding<Habit>
并且可以将其传递给
EditHabitView

在第二个代码块中,您只需将

ForEach
中的变量从
habit
重命名为
$habit
-
$
不会更改变量的类型。无论哪种方式都是
Binding<Habit>

在“混合”代码块中,您会收到错误,因为您已将闭包参数声明为

$habit
,但随后在第二个“混合”代码块中引用了未定义的变量
habit
,反之亦然。

© www.soinside.com 2019 - 2024. All rights reserved.