从目标视图更新 SwiftUI 列表数据会导致意外行为
Posted
技术标签:
【中文标题】从目标视图更新 SwiftUI 列表数据会导致意外行为【英文标题】:SwiftUI List data update from destination view causes unexpected behaviour 【发布时间】:2021-05-09 18:26:01 【问题描述】:我遇到了这种相当常见的情况,List
将来自@ObservedObject
数据存储的一些项目显示为NavigationLinks
。
在选择NavigationLink
时,会显示一个DetailView。这个视图有一个简单的 Toggle
连接到其 ViewModel 类上的 @Published
变量。
当 DetailView 出现 (onAppear:
) 时,它的 View Model 将控制 Toggle
的已发布 var
设置为 true
并触发一个异步请求,该请求将更新 主数据存储,导致上一个屏幕中的List
也更新。
问题在于,当这种情况发生时(List
是从 Detail View 中触发的操作中重新加载),DetailViewModel 的多个实例似乎被保留,并且 DetailView 开始从错误的发布者接收事件>.
在第一次到达详细信息屏幕时,行为是正确的(如下面的代码所示),切换设置为 true
并且 商店 更新,但是,在导航回到List
,然后再回到另一个DetailView,在出现时切换设置为true
,但这次当重新加载store的代码执行时,切换设置回false
.
我的理解是,当重新加载 List
并创建新的 DetailView 和 ViewModel(作为 NavigationLink
的目标)时,来自控制 Toggle
的 isOn
变量的初始值(即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"]
【问题讨论】:
【参考方案1】:您控制生命周期和对象的方式不是此 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) 创建一个容器对象,例如提供 ViewModels 的工厂,并通过环境将其传递给视图,或者 (c) 执行a 和 b 都有。
如果您在另一个类中引用存储,请保持弱引用 weak var store: Store?
。如果您在@Published 上保持该RootStore 中的状态,那么您可以直接订阅该发布者或RootStore 自己的ObjectWillChangePublisher。您还可以使用 CurrentValueSubject 等其他 Combine 发布者来实现与 @Published 相同的效果。
代码示例见
Setting a StateObject value from child view causes NavigationView to pop all views
SwiftUI @Published Property Being Updated In DetailView
【讨论】:
以上是关于从目标视图更新 SwiftUI 列表数据会导致意外行为的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI @Published 属性正在 DetailView 中更新
合并SwiftUI远程获取数据-ObjectBinding不会更新视图