来自目标视图的 SwiftUI 列表数据更新导致意外行为
SwiftUI List data update from destination view causes unexpected behaviour
我有这种相当常见的情况,其中 List
将 @ObservedObject
数据存储中的一些项目显示为 NavigationLinks
。
选择 NavigationLink
时会显示 DetailView。此视图在其 ViewModel class.
上有一个简单的 Toggle
连接到 @Published
var
当 DetailView 出现时 (onAppear:
) 它的视图模型将控制 Toggle
的已发布 var
设置为 true
并且还会触发将更新 主数据存储 的异步请求,从而导致前一屏幕中的 List
也更新。
问题是当发生这种情况时(List
从详细视图中触发的操作重新加载)DetailViewModel 的多个实例似乎被保留并且 DetailView 开始从错误的 出版商.
第一次到达详细信息屏幕时行为是正确的(如下面的代码所示),切换设置为 true
并且 store但是,在导航回 List
然后再次导航到另一个 DetailView 时更新,切换设置为 true
出现,但这次是重新加载 store[= 的代码55=] 执行,切换设置回 false
.
我的理解是,当重新加载 List
并创建新的 DetailView 和 ViewModel(作为 NavigationLink
的目标)时,isOn
变量的初始值控制 Toggle
(即 false
)以某种方式触发对当前显示屏幕的 Toggle
的更新。
我是不是漏掉了什么?
import SwiftUI
import Combine
class Store: ObservableObject {
static var shared: Store = .init()
@Published var items = ["one", "two"]
private init() { }
}
struct ContentView: View {
@ObservedObject var store = Store.shared
var body: some View {
NavigationView {
List(store.items, id: \.self) { item in
NavigationLink(item, destination: ItemDetailView())
}
}
}
}
struct ItemDetailView: View {
@ObservedObject var viewModel = ItemDetailViewModel()
var body: some View {
VStack {
Toggle("A toggle", isOn: $viewModel.isOn)
Text(viewModel.title)
Spacer()
} .onAppear(perform: viewModel.onAppear)
}
}
class ItemDetailViewModel: ObservableObject {
@ObservedObject var store: Store = .shared
@Published var isOn = false
var title: String {
store.items[0]
}
func onAppear() {
isOn = true
asyncOperationThatUpdatesTheStoreData()
}
private func asyncOperationThatUpdatesTheStoreData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
self?.store.items = ["three", "four"]
}
}
}
您控制生命周期和对象的方式不是此 UI 框架中的模式。 VieModel 不会神奇地重新发布单例值类型;它将使用该值实例化,然后改变其状态,而无需再次检查共享实例,除非它被重建。
class Store: ObservableObject {
static var shared: Store = .init()
struct ContentView: View {
@ObservedObject var store = Store.shared
struct ItemDetailView: View {
@ObservedObject var viewModel = ItemDetailViewModel()
class ViewModel: ObservableObject {
@Published var items: [String] = Store.shared.items
有很多潜在的可行模式。例如:
创建 class RootStore: ObservableObject
并将其作为 @StateObject 放置在 App 中。 StateObject 的生命周期是该视图层次结构的生命周期。您可以 (a) 通过 .environmentObject(store) 直接将其公开给子视图或 (b) 创建一个容器对象,例如出售 ViewModel 的工厂并通过环境传递它而不将商店公开给视图,或 (c) 做a 和 b.
如果您引用另一个 class 中的商店,请保持弱引用 weak var store: Store?
。如果您在 @Published 上的 RootStore 中保持状态,那么您可以直接订阅该发布者或 RootStore 自己的 ObjectWillChangePublisher。您还可以使用其他 Combine 发布者(例如 CurrentValueSubject)来实现与 @Published 相同的效果。
有关代码示例,请参阅
我有这种相当常见的情况,其中 List
将 @ObservedObject
数据存储中的一些项目显示为 NavigationLinks
。
选择 NavigationLink
时会显示 DetailView。此视图在其 ViewModel class.
Toggle
连接到 @Published
var
当 DetailView 出现时 (onAppear:
) 它的视图模型将控制 Toggle
的已发布 var
设置为 true
并且还会触发将更新 主数据存储 的异步请求,从而导致前一屏幕中的 List
也更新。
问题是当发生这种情况时(List
从详细视图中触发的操作重新加载)DetailViewModel 的多个实例似乎被保留并且 DetailView 开始从错误的 出版商.
第一次到达详细信息屏幕时行为是正确的(如下面的代码所示),切换设置为 true
并且 store但是,在导航回 List
然后再次导航到另一个 DetailView 时更新,切换设置为 true
出现,但这次是重新加载 store[= 的代码55=] 执行,切换设置回 false
.
我的理解是,当重新加载 List
并创建新的 DetailView 和 ViewModel(作为 NavigationLink
的目标)时,isOn
变量的初始值控制 Toggle
(即 false
)以某种方式触发对当前显示屏幕的 Toggle
的更新。
我是不是漏掉了什么?
import SwiftUI
import Combine
class Store: ObservableObject {
static var shared: Store = .init()
@Published var items = ["one", "two"]
private init() { }
}
struct ContentView: View {
@ObservedObject var store = Store.shared
var body: some View {
NavigationView {
List(store.items, id: \.self) { item in
NavigationLink(item, destination: ItemDetailView())
}
}
}
}
struct ItemDetailView: View {
@ObservedObject var viewModel = ItemDetailViewModel()
var body: some View {
VStack {
Toggle("A toggle", isOn: $viewModel.isOn)
Text(viewModel.title)
Spacer()
} .onAppear(perform: viewModel.onAppear)
}
}
class ItemDetailViewModel: ObservableObject {
@ObservedObject var store: Store = .shared
@Published var isOn = false
var title: String {
store.items[0]
}
func onAppear() {
isOn = true
asyncOperationThatUpdatesTheStoreData()
}
private func asyncOperationThatUpdatesTheStoreData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
self?.store.items = ["three", "four"]
}
}
}
您控制生命周期和对象的方式不是此 UI 框架中的模式。 VieModel 不会神奇地重新发布单例值类型;它将使用该值实例化,然后改变其状态,而无需再次检查共享实例,除非它被重建。
class Store: ObservableObject {
static var shared: Store = .init()
struct ContentView: View {
@ObservedObject var store = Store.shared
struct ItemDetailView: View {
@ObservedObject var viewModel = ItemDetailViewModel()
class ViewModel: ObservableObject {
@Published var items: [String] = Store.shared.items
有很多潜在的可行模式。例如:
创建
class RootStore: ObservableObject
并将其作为 @StateObject 放置在 App 中。 StateObject 的生命周期是该视图层次结构的生命周期。您可以 (a) 通过 .environmentObject(store) 直接将其公开给子视图或 (b) 创建一个容器对象,例如出售 ViewModel 的工厂并通过环境传递它而不将商店公开给视图,或 (c) 做a 和 b.如果您引用另一个 class 中的商店,请保持弱引用
weak var store: Store?
。如果您在 @Published 上的 RootStore 中保持状态,那么您可以直接订阅该发布者或 RootStore 自己的 ObjectWillChangePublisher。您还可以使用其他 Combine 发布者(例如 CurrentValueSubject)来实现与 @Published 相同的效果。
有关代码示例,请参阅