SwiftUI视图不更新问题:ViewModel State内类数据变更无响应
SwiftUI: 嵌套ObservableObject导致视图不更新的问题
问题描述
要求ContentView在ContentViewViewModel.state.itemManager.items数组更新时自动刷新,但将数据逻辑移到ItemManager类后,点击"Add Item"按钮添加元素,视图不会更新(直接把items放在ContentViewState里时正常)。
代码示例
struct ContentView: View { @ObservedObject private var viewModel = ContentViewViewModel() var body: some View { VStack { ForEach(viewModel.state.itemManager.items, id: \.id) { item in Text(item.value) } Button("Add Item") { viewModel.triggerAddButton() } } } } class ContentViewViewModel: ObservableObject { @Published var state = ContentViewState() func triggerAddButton() { state.itemManager.addItem() } } struct ContentViewState { let itemManager = ItemManager() } class ItemManager: ObservableObject { @Published var items: [Item] = [Item("Item 1"), Item("Item 2")] func addItem() { items.append(.init("NewItem")) } } struct Item: Identifiable { var id: UUID var value: String init(_ value: String) { self.value = value self.id = UUID() } }
为什么会这样?
@Published只会监听属性本身的变化,不会追踪嵌套引用类型的内部变更:
ContentViewViewModel的state是值类型(struct),@Published只会在state被整体替换时发送通知;state里的itemManager是引用类型(ObservableObject),当它内部的items更新时,state本身的内存值并没有改变(引用指针没变化);- 所以
ContentViewViewModel不会向视图发送刷新信号,视图自然不会更新。
修复方案
方案1:让ContentViewState成为ObservableObject并@Published ItemManager
把ContentViewState改成类,让它遵循ObservableObject,并将itemManager标记为@Published,这样itemManager的变更会向上传递:
class ContentViewViewModel: ObservableObject { @Published var state = ContentViewState() func triggerAddButton() { state.itemManager.addItem() } } class ContentViewState: ObservableObject { @Published var itemManager = ItemManager() } // 其余代码保持不变
方案2:手动转发ItemManager的变更通知
在ContentViewViewModel中监听ItemManager的objectWillChange信号,手动触发自身的变更通知:
import Combine class ContentViewViewModel: ObservableObject { var state = ContentViewState() private var cancellables = Set<AnyCancellable>() init() { state.itemManager.objectWillChange .sink { [weak self] _ in self?.objectWillChange.send() } .store(in: &cancellables) } func triggerAddButton() { state.itemManager.addItem() } } // ContentViewState保持struct不变 struct ContentViewState { let itemManager = ItemManager() }
方案3:直接在视图中观察ItemManager
如果可以接受视图和数据层的耦合,也可以直接在ContentView中监听ItemManager的变更:
struct ContentView: View { @ObservedObject private var viewModel = ContentViewViewModel() private var itemManager: ItemManager { viewModel.state.itemManager } var body: some View { VStack { ForEach(itemManager.items, id: \.id) { item in Text(item.value) } Button("Add Item") { viewModel.triggerAddButton() } } .onReceive(itemManager.objectWillChange) { _ in // 接收变更信号,触发视图刷新 } } }
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

