SwiftUI:选择列表中的项目后更新数据
SwiftUI: Update data after item in list is selected
我花了两天时间想弄明白这个问题,希望有人能帮助我。很确定这个基本的东西一定是可行的,我缺少一些基本概念...
在 macOS 上使用 XCode 11.4,但我怀疑这也适用于 iOS。
我已将源代码缩减到最低限度以说明问题。
我在主细节视图中有一个项目列表,其中项目列表是从一个来源获取的,一旦用户点击其中一个,详细信息就会从第二个来源获取并显示在右侧细节视图。
预期结果
当我点击一个项目时,项目的正文显示在右侧的详细视图中。
实际结果
当我点击第一项时,正确的正文显示在右边。但是 ItemDetailStore.load() 方法被调用了两次!
当我点击第二个和第三个项目时,详细视图保持不变。
来源
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())
}
}
我花了两天时间想弄明白这个问题,希望有人能帮助我。很确定这个基本的东西一定是可行的,我缺少一些基本概念...
在 macOS 上使用 XCode 11.4,但我怀疑这也适用于 iOS。
我已将源代码缩减到最低限度以说明问题。
我在主细节视图中有一个项目列表,其中项目列表是从一个来源获取的,一旦用户点击其中一个,详细信息就会从第二个来源获取并显示在右侧细节视图。
预期结果
当我点击一个项目时,项目的正文显示在右侧的详细视图中。
实际结果
当我点击第一项时,正确的正文显示在右边。但是 ItemDetailStore.load() 方法被调用了两次!
当我点击第二个和第三个项目时,详细视图保持不变。
来源
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())
}
}