SwiftUI的列表如何更新选择绑定?

4
我真的很难理解List(selection:)如何导致选择绑定的更新。
我正在分析苹果示例代码,位于https://developer.apple.com/documentation/swiftui/building_a_great_mac_app_with_swiftui
项目是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 = $0 })
    }

    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,从而使GardenDetail视图使用新选择进行更新。

我的问题是:点击侧边栏项目中的一个如何导致选择绑定被更新,因为没有对选择绑定的引用?是否与 ForEach 有关?

如果是,如何添加不参与此类循环的“即席”项?如果不是,发生了什么?


尝试使用选择器设置ID,代码如下:List(d: \.self, selection: $selection)) - Raja Kishan
@RajaKishan,您能否详细说明一下?它是如何更新选择绑定的? - serlingpa
2个回答

5

ListForEach 在这里协同工作。列表看到它内部有一个针对 GardenForEach,而 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... 您只需找出在哪里解码这些自定义标签以及如何在详细视图中相应地显示即可。

太棒了,它能正常工作。谢谢!不过我有一个问题:你是怎么知道这个的?苹果的文档真的很糟糕。我不知道我应该从哪里获取这种信息。他们的文档中没有任何用法示例。 - serlingpa
好问题 :) https://www.hackingwithswift.com/100/swiftui 是一个很棒的基础课程。而对于高级主题:https://swiftui-lab.com。他还有一个应用程序,作为文档来源非常好用...虽然需要一点投资,但是非常值得。当然...还要不断练习。 - ChrisR
是的,我快完成了使用SwiftUI进行100天黑客马拉松的活动!我已经拥有他所有的书籍,但现在真的需要参考资料。我将尝试你建议的实验室。谢谢! - serlingpa

1
您可以在List文档中找到更多细节,查看“支持多维列表”下的示例。
但基本上,发生了以下几件事情:
  1. 代码行List(selection: $selection)将选定的园林存储在SideBarselection变量中。

  2. SideBarselection变量是从ContentView中的selection变量(参见代码行Sidebar(selection: selection))绑定而来的,因此当前者发生变化时,后者也会更新。

  3. ContentViewselectedGarden变量是一个计算属性,它返回selection.wrappedValue索引处的园林 - 如果selection的值发生变化,则selectedGarden将返回不同的值。

  4. GardenDetail视图根据传递给它的selectedGarden变量显示详细信息:GardenDetail(garden: selectedGarden)

SideBar中更改selection也会更改ContenView中的selection,这会使selectedGarden返回不同的园林,从而更新GardenDetail视图。

谢谢,但我已经理解了所有内容。我不明白的是侧边栏如何更新传递给它的选择绑定。当您单击侧边栏项目时,某种方式会更新选择绑定,并按您所描述的显示花园详细信息。侧边栏如何更新选择绑定?除了在“List(selection:$ selection)”中提到之外,侧边栏中没有提到此绑定。是什么在更新它? - serlingpa
正是代码中的那一行 List(selection: $selection) 读取了所选项目。我上面提到的苹果文档解释道:"要使列表成员可选择,请提供一个绑定到选择变量的绑定。将绑定到列表数据的单个实例的 Identifiable.ID 类型会创建一个单选列表"。基本上,您选择一个项目,然后 selection 变量存储其 ID。 - HunterLion
好的,我也理解了!有一点我不太明白,而且似乎我表达得不够清楚。当你在侧边栏中点击一个项目时,我希望看到类似于“selection = clickedGarden”的东西。我在列表中看到了selectkon绑定,“List(selection: $selection)”,但是我没有看到任何地方改变绑定的值。显然这是自动发生的,那么在更新绑定时,框架如何知道为选择绑定分配哪个新值? - serlingpa
只有一个包含ForEach的DisclosureGroup。没有指示任何东西的哪个属性是应该在选择项目时分配到绑定的ID。我只能想到ForEach提供了ID;如果是这样,那么我如何向我的List添加不参与ForEach的常量项? - serlingpa
我认为你忽略了List本身就是一个视图结构体。它会跟踪你传递给它的所有内容。有很多事情在幕后发生,你看不到。我记不清是谁了,但其中一位博主做了一系列的帖子,在SwiftUI中重新实现了不同的视图结构体。显然,它们并不完全像苹果的,但它们揭示了一些这些机制的工作方式。 - Yrb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接