SwiftUI:选择列表中的项目后更新数据

SwiftUI: Update data after item in list is selected

我花了两天时间想弄明白这个问题,希望有人能帮助我。很确定这个基本的东西一定是可行的,我缺少一些基本概念...

macOS 上使用 XCode 11.4,但我怀疑这也适用于 iOS。

我已将源代码缩减到最低限度以说明问题。

我在主细节视图中有一个项目列表,其中项目列表是从一个来源获取的,一旦用户点击其中一个,详细信息就会从第二个来源获取并显示在右侧细节视图。

预期结果

当我点击一个项目时,项目的正文显示在右侧的详细视图中。

实际结果

  1. 当我点击第一项时,正确的正文显示在右边。但是 ItemDetailStore.load() 方法被调用了两次!

  2. 当我点击第二个和第三个项目时,详细视图保持不变。

来源

import SwiftUI

struct ContentView: View {
    @State var selectedItem: Item?

    var body: some View {
        NavigationView {
            ItemList(selectedItem: $selectedItem)
            if selectedItem != nil {
                ItemDetail(selectedItem: $selectedItem)
            }
        }
    }
}

struct ItemList: View {
    @Binding var selectedItem: Item?
    var items = [
        Item(itemId: 0, title: "Item #0", body: "Empty."),
        Item(itemId: 1, title: "Item #1", body: "Empty."),
        Item(itemId: 2, title: "Item #2", body: "Empty.")
    ]

    var body: some View {
        List(selection: $selectedItem) {
            ForEach(Array(items.enumerated()), id: \.element) { index, item in
                ItemRow(item: item)
          }
        }
        .listStyle(SidebarListStyle())
    }
}

struct ItemRow: View {
    var item: Item

    var body: some View {
        Text("\(item.title)")
            .padding(12)
    }
}

struct ItemDetail: View {
    @EnvironmentObject var itemDetailStore: ItemDetailStore
    @Binding var selectedItem: Item?

    var body: some View {
        VStack {
            if itemDetailStore.items.count > 0 {
                Text(itemDetailStore.items[0].body)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onAppear {
            if self.selectedItem != nil {
                self.itemDetailStore.load(id: self.selectedItem!.itemId)
            }
        }
    }
}

struct Item: Codable, Hashable {
    var itemId: Int
    var title: String
    var body: String
}

class ItemDetailStore: ObservableObject {
    @Published var items = [Item]()

    var itemsList = [
        Item(itemId: 0, title: "Item #0", body: "This is an excellent item #0."),
        Item(itemId: 1, title: "Item #1", body: "This is an excellent item #1."),
        Item(itemId: 2, title: "Item #2", body: "This is an excellent item #2.")
    ]

    func load(id: Int) {
        self.items = [itemsList[id]]
        print("\(self.items[0].title) loaded.")
    }
}

然后在 AppDelegate 中注入环境:

let contentView = ContentView().environmentObject(ItemDetailStore())

罪魁祸首似乎是 ItemDetail 中的 onAppear 修饰符,因为它仅在单击列表中的第一项时调用。每次 selectedItem 更改时视图不应该更新吗?

非常感谢任何反馈。

看看这个:

import SwiftUI


struct ContentView: View {

    @EnvironmentObject var itemDetailStore: ItemDetailStore

    var body: some View {
        NavigationView {
            ItemList()
            if self.itemDetailStore.selectedItem != nil {
                ItemDetail()
            }
        }
    }
}

struct ItemList: View {

    @EnvironmentObject var itemDetailStore: ItemDetailStore

    var items = [
        Item(itemId: 0, title: "Item #0", body: "Empty."),
        Item(itemId: 1, title: "Item #1", body: "Empty."),
        Item(itemId: 2, title: "Item #2", body: "Empty.")
    ]

    var body: some View {
        List(selection: self.$itemDetailStore.selectedItem) {
            ForEach(Array(items.enumerated()), id: \.element) { index, item in
                ItemRow(item: item)
          }
        }
        .listStyle(SidebarListStyle())
    }
}

struct ItemRow: View {
    var item: Item

    var body: some View {
        Text("\(item.title)")
            .padding(12)
    }
}

struct ItemDetail: View {
    @EnvironmentObject var itemDetailStore: ItemDetailStore

    var body: some View {
        VStack {
            if itemDetailStore.items.count > 0 {
                Text(itemDetailStore.items[0].body)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onAppear {

        }
    }
}

struct Item: Codable, Hashable, Identifiable {
    var id = UUID().uuidString

    var itemId: Int
    var title: String
    var body: String
}

class ItemDetailStore: ObservableObject {
    @Published var items = [Item]()
    @Published var selectedItem: Item? {
        didSet {
            if self.selectedItem != nil {
                self.load(id: self.selectedItem!.itemId)
            }
        }
    }

    var itemsList = [
        Item(itemId: 0, title: "Item #0", body: "This is an excellent item #0."),
        Item(itemId: 1, title: "Item #1", body: "This is an excellent item #1."),
        Item(itemId: 2, title: "Item #2", body: "This is an excellent item #2.")
    ]

    func load(id: Int) {
        self.items = [itemsList[id]]
        print("\(self.items[0].title) loaded.")
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(ItemDetailStore())
    }
}