来自目标视图的 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   

有很多潜在的可行模式。例如:

  1. 创建 class RootStore: ObservableObject 并将其作为 @StateObject 放置在 App 中。 StateObject 的生命周期是该视图层次结构的生命周期。您可以 (a) 通过 .environmentObject(store) 直接将其公开给子视图或 (b) 创建一个容器对象,例如出售 ViewModel 的工厂并通过环境传递它而不将商店公开给视图,或 (c) 做a 和 b.

  2. 如果您引用另一个 class 中的商店,请保持弱引用 weak var store: Store?。如果您在 @Published 上的 RootStore 中保持状态,那么您可以直接订阅该发布者或 RootStore 自己的 ObjectWillChangePublisher。您还可以使用其他 Combine 发布者(例如 CurrentValueSubject)来实现与 @Published 相同的效果。

有关代码示例,请参阅