You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过MVVM结合@ObservedObject触发SwiftUI自动更新

SwiftUI + MVVM 实现方案及UI不更新问题修复

SwiftUI与MVVM结合完全可行,你的代码问题出在ViewModel对Model层变化的监听逻辑上,以下是具体分析和修复方案:

问题根源

你的ViewModel中使用@Published var number: NumberStorage,但NumberStorage是引用类型(ObservableObject类),@Published只会在number的引用发生改变时(比如重新赋值一个新的NumberStorage实例)才会发送更新通知。而你调用increaseNumber()时,只是修改了NumberStorage内部numberObject的属性,并没有改变number的引用,因此ViewModel不会通知View更新。

而直接在View中使用@ObservedObject var numberStorage = NumberStorage()能正常工作,是因为NumberStorage内部的@Published var numberObject会在结构体实例更新时发送通知,View能直接接收到。

修复方案

方案1:ViewModel转发Model层的更新通知

让ViewModel订阅NumberStorage的变化,当Model内部属性更新时,手动触发ViewModel的objectWillChange通知:

class ViewModel: ObservableObject {
    private let numberStorage = NumberStorage()
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 订阅NumberStorage的numberObject变化,触发ViewModel的更新
        numberStorage.$numberObject
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }
            .store(in: &cancellables)
    }
    
    func increaseNumber() {
        numberStorage.increaseNumber()
    }
    
    func getNumberObject() -> NumberObject {
        numberStorage.numberObject
    }
}

View中使用ViewModel的代码恢复注释部分即可:

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("\(viewModel.getNumberObject().number)")
                .padding()
            Button("IncreaseNumber") {
                viewModel.increaseNumber()
            }
        }
    }
}

方案2:简化ViewModel,直接暴露可观察的数值

更符合MVVM的实践是让ViewModel只暴露View需要的状态,而不是传递整个Model对象,这样能降低耦合:

class ViewModel: ObservableObject {
    private let numberStorage = NumberStorage()
    // 直接暴露当前数值,View无需关心Model结构
    @Published private(set) var currentNumber: Int = 0
    
    init() {
        // 同步NumberStorage的数值变化到currentNumber
        numberStorage.$numberObject
            .map { $0.number }
            .assign(to: &$currentNumber)
    }
    
    func increaseNumber() {
        numberStorage.increaseNumber()
    }
}

View中直接使用currentNumber即可:

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("\(viewModel.currentNumber)")
                .padding()
            Button("IncreaseNumber") {
                viewModel.increaseNumber()
            }
        }
    }
}

方案3:调整Model层结构(可选)

如果你的业务逻辑允许,可以简化Model层,直接让ViewModel持有可观察的数值,去掉NumberStorage层级:

// ViewModel
class ViewModel: ObservableObject {
    @Published private(set) var number = 0
    
    func increaseNumber() {
        number += 1
    }
}

// View
struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("\(viewModel.number)")
                .padding()
            Button("IncreaseNumber") {
                viewModel.increaseNumber()
            }
        }
    }
}

这种方式更简洁,适合简单的状态管理场景。

内容的提问来源于stack exchange,提问作者Jonas Bäumer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 01:50:50