SwiftUI 的 List 如何更新选择绑定?
How does SwiftUI's List update the selection binding?
我真的很难理解 List(selection:)
如何导致对选择绑定的更新。
我正在 https://developer.apple.com/documentation/swiftui/building_a_great_mac_app_with_swiftui
解开 Apple 示例代码
项目是Session2/Part2-End/GardenApp.xcodeproj
左边是侧边栏,右边是主要的细节视图。 ContentView
看起来像这样:
struct ContentView: View {
@EnvironmentObject var store: Store
@SceneStorage("selection") private var selectedGardenID: Garden.ID?
@AppStorage("defaultGarden") private var defaultGardenID: Garden.ID?
var body: some View {
NavigationView {
Sidebar(selection: selection)
GardenDetail(garden: selectedGarden)
}
}
private var selection: Binding<Garden.ID?> {
Binding(get: { selectedGardenID ?? defaultGardenID }, set: { selectedGardenID = [=10=] })
}
private var selectedGarden: Binding<Garden> {
$store[selection.wrappedValue]
}
}
侧边栏是这样的:
struct Sidebar: View {
@EnvironmentObject var store: Store
@SceneStorage("expansionState") var expansionState = ExpansionState()
@Binding var selection: Garden.ID?
var body: some View {
List(selection: $selection) {
DisclosureGroup(isExpanded: $expansionState[store.currentYear]) {
ForEach(store.gardens(in: store.currentYear)) { garden in
SidebarLabel(garden: garden)
.badge(garden.numberOfPlantsNeedingWater)
}
} label: {
Label("Current", systemImage: "chart.bar.doc.horizontal")
}
Section("History") {
GardenHistoryOutline(range: store.previousYears, expansionState: $expansionState)
}
}
.frame(minWidth: 250)
}
}
现在 SidebarLabel
视图没有任何对选择绑定的引用;但是,当您单击其中一个标签时,选择会更新并且绑定会传播到 ContentView
,因此 GardenDetai
视图会更新为新选择。
我的问题是:由于没有对选择绑定的引用,单击其中一个侧边栏项目如何导致选择绑定更新? ForEach
和它有什么关系吗?
如果是这样,我如何添加 'ad hoc' 不参与 ForEach
的项目?如果不是,那是怎么回事?
您可以在 documentation for List 中找到更多详细信息,查看“支持 Multi-Dimensional 列表”下的示例。
但基本上,这就是正在发生的事情:
行List(selection: $selection)
将选择的花园存储在SideBar
的selection
变量中。
SideBar
的selection
变量是ContentView
中selection
变量的绑定 (参见第 Sidebar(selection: selection)
行),因此当前者发生变化时,后者也会更新。
ContentView
的 selectedGarden
变量是计算得到的 属性 return 索引为 selection.wrappedValue
的花园 -如果 selection
的值发生变化,selectedGarden
return 将是一个不同的值。
GardenDetail
视图根据传递给它的 selectedGarden
变量显示详细信息:GardenDetail(garden: selectedGarden)
.
改变 SideBar
中的 selection
也会改变 ContenView
中的 selection
,这使得 selectedGarden
return 成为一个不同的花园更新 GardenDetail
视图。
List
和 ForEach
在这里一起工作。列表看到里面有个ForEach over Garden,ForEach自动提供了一个.tag(garden.id)
在纯 List
形式中,它看起来像这样,并且可能更清楚地显示发生了什么。我包含了一个明确的 .tag()
但这将自动生成。
struct Sidebar2: View {
@EnvironmentObject var store: Store
@Binding var selection: Garden.ID?
var body: some View {
List(store.gardens(in: store.currentYear), selection: $selection) { garden in
SidebarLabel(garden: garden)
.tag(garden.id) // << not needed, will be done implicitly
.badge(garden.numberOfPlantsNeedingWater)
}
.frame(minWidth: 250)
}
}
由于这些部分,List
被用作 ForEach
的包装。但是原理还是一样的。 ForEach
将其自动生成的 id
交给 List
并设置 selection
.
如果您想将自定义元素添加到侧边栏,您可以给它们一个明确的 .tag
...您只需要找出在哪里解码这些自定义标签以及在详细视图中相应地显示什么.
我真的很难理解 List(selection:)
如何导致对选择绑定的更新。
我正在 https://developer.apple.com/documentation/swiftui/building_a_great_mac_app_with_swiftui
解开 Apple 示例代码项目是Session2/Part2-End/GardenApp.xcodeproj
左边是侧边栏,右边是主要的细节视图。 ContentView
看起来像这样:
struct ContentView: View {
@EnvironmentObject var store: Store
@SceneStorage("selection") private var selectedGardenID: Garden.ID?
@AppStorage("defaultGarden") private var defaultGardenID: Garden.ID?
var body: some View {
NavigationView {
Sidebar(selection: selection)
GardenDetail(garden: selectedGarden)
}
}
private var selection: Binding<Garden.ID?> {
Binding(get: { selectedGardenID ?? defaultGardenID }, set: { selectedGardenID = [=10=] })
}
private var selectedGarden: Binding<Garden> {
$store[selection.wrappedValue]
}
}
侧边栏是这样的:
struct Sidebar: View {
@EnvironmentObject var store: Store
@SceneStorage("expansionState") var expansionState = ExpansionState()
@Binding var selection: Garden.ID?
var body: some View {
List(selection: $selection) {
DisclosureGroup(isExpanded: $expansionState[store.currentYear]) {
ForEach(store.gardens(in: store.currentYear)) { garden in
SidebarLabel(garden: garden)
.badge(garden.numberOfPlantsNeedingWater)
}
} label: {
Label("Current", systemImage: "chart.bar.doc.horizontal")
}
Section("History") {
GardenHistoryOutline(range: store.previousYears, expansionState: $expansionState)
}
}
.frame(minWidth: 250)
}
}
现在 SidebarLabel
视图没有任何对选择绑定的引用;但是,当您单击其中一个标签时,选择会更新并且绑定会传播到 ContentView
,因此 GardenDetai
视图会更新为新选择。
我的问题是:由于没有对选择绑定的引用,单击其中一个侧边栏项目如何导致选择绑定更新? ForEach
和它有什么关系吗?
如果是这样,我如何添加 'ad hoc' 不参与 ForEach
的项目?如果不是,那是怎么回事?
您可以在 documentation for List 中找到更多详细信息,查看“支持 Multi-Dimensional 列表”下的示例。
但基本上,这就是正在发生的事情:
行
List(selection: $selection)
将选择的花园存储在SideBar
的selection
变量中。SideBar
的selection
变量是ContentView
中selection
变量的绑定 (参见第Sidebar(selection: selection)
行),因此当前者发生变化时,后者也会更新。ContentView
的selectedGarden
变量是计算得到的 属性 return 索引为selection.wrappedValue
的花园 -如果selection
的值发生变化,selectedGarden
return 将是一个不同的值。GardenDetail
视图根据传递给它的selectedGarden
变量显示详细信息:GardenDetail(garden: selectedGarden)
.
改变 SideBar
中的 selection
也会改变 ContenView
中的 selection
,这使得 selectedGarden
return 成为一个不同的花园更新 GardenDetail
视图。
List
和 ForEach
在这里一起工作。列表看到里面有个ForEach over Garden,ForEach自动提供了一个.tag(garden.id)
在纯 List
形式中,它看起来像这样,并且可能更清楚地显示发生了什么。我包含了一个明确的 .tag()
但这将自动生成。
struct Sidebar2: View {
@EnvironmentObject var store: Store
@Binding var selection: Garden.ID?
var body: some View {
List(store.gardens(in: store.currentYear), selection: $selection) { garden in
SidebarLabel(garden: garden)
.tag(garden.id) // << not needed, will be done implicitly
.badge(garden.numberOfPlantsNeedingWater)
}
.frame(minWidth: 250)
}
}
由于这些部分,List
被用作 ForEach
的包装。但是原理还是一样的。 ForEach
将其自动生成的 id
交给 List
并设置 selection
.
如果您想将自定义元素添加到侧边栏,您可以给它们一个明确的 .tag
...您只需要找出在哪里解码这些自定义标签以及在详细视图中相应地显示什么.