ObservableObject中@Published更新后SwiftUI View未刷新问题排查
我知道已有很多类似问题,但大多是引用/值类型的问题,我的情况不一样。我通过UIHostingController在UIViewController中嵌入了SwiftUI视图,使用了ObservableObject类型的ViewModel,这个ViewModel在ViewController中实例化后传递给SwiftUI视图。但更新ObservableObject中的@Published属性时,SwiftUI视图并未同步刷新,请问哪里出错了?
ViewController代码
class DeveloperScreenViewController1: UIViewController { let developerScreenViewModel: DeveloperScreenViewModel private lazy var contentView: UIHostingController = { UIHostingController(rootView: DeveloperScreenView(developerScreenViewModel: developerScreenViewModel)) }() init(viewModel: DeveloperScreenViewModel) { self.developerScreenViewModel = viewModel super.init(nibName: nil, bundle: Bundle.main) } override func viewDidLoad() { super.viewDidLoad() setupConstraints() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.developerScreenViewModel.setup() } private func setupConstraints() { // add swiftUI view as a subview addChild(self.contentView) self.contentView.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(self.contentView.view) // set constraints to match the parent viewcontroller's view NSLayoutConstraint.activate([ view.leadingAnchor.constraint(equalTo: contentView.view.leadingAnchor), view.trailingAnchor.constraint(equalTo: contentView.view.trailingAnchor), view.topAnchor.constraint(equalTo: contentView.view.topAnchor), view.bottomAnchor.constraint(equalTo: contentView.view.bottomAnchor) ]) }
ViewModel代码
class DeveloperScreenViewModel: ObservableObject { @Published var environments: [Endpoint] = [] @Published var realTimeUpdateToggle:Bool = false public init() { // self.setup() } public func setup() { self.initializeListItems() self.initializeRealTimeToggle() } private func initializeListItems() { self.environments = EnvironmentStore.shared.changeableEndpoints } func initializeRealTimeToggle() { realTimeUpdateToggle = RealTimeUpdatesDeveloperUtils.isEnabled() }
SwiftUI View代码
struct DeveloperScreenView: View { @ObservedObject var developerScreenViewModel: DeveloperScreenViewModel var body: some View { VStack { DeveloperScreenEnvironmentView(endPoints: developerScreenViewModel.environments) DeveloperScreenRealTimeUpdateToggleView(realTimeToggleItem: developerScreenViewModel.realTimeUpdateToggle) } }
问题原因及修复方案
核心问题1:子视图接收的是值拷贝而非绑定
你的DeveloperScreenEnvironmentView和DeveloperScreenRealTimeUpdateToggleView直接接收的是属性的值拷贝,没有和ViewModel建立响应式关联。当ViewModel的@Published属性更新时,子视图持有的是旧值的副本,自然无法感知变化。
核心问题2:布局约束方向写反
setupConstraints中的约束逻辑错误,将父视图的锚点绑定到子视图,会导致布局异常,甚至可能隐藏SwiftUI视图(虽不是刷新失效的直接原因,但会引发其他UI问题)。
具体修复步骤:
子视图改用Binding传递参数
修改子视图的输入参数为Binding类型,让它们直接绑定到ViewModel的@Published属性:// 修改DeveloperScreenEnvironmentView struct DeveloperScreenEnvironmentView: View { @Binding var endPoints: [Endpoint] // 其余视图逻辑保持不变 } // 修改DeveloperScreenRealTimeUpdateToggleView struct DeveloperScreenRealTimeUpdateToggleView: View { @Binding var realTimeToggleItem: Bool // 其余视图逻辑保持不变 } // 父SwiftUI视图中传递绑定 struct DeveloperScreenView: View { @ObservedObject var developerScreenViewModel: DeveloperScreenViewModel var body: some View { VStack { DeveloperScreenEnvironmentView(endPoints: $developerScreenViewModel.environments) DeveloperScreenRealTimeUpdateToggleView(realTimeToggleItem: $developerScreenViewModel.realTimeUpdateToggle) } } }修正布局约束方向
调换锚点绑定顺序,确保子视图对齐父视图的边缘:NSLayoutConstraint.activate([ contentView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), contentView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), contentView.view.topAnchor.constraint(equalTo: view.topAnchor), contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) ])可选:确保更新在主线程执行
如果setup方法或后续属性更新是在后台线程触发的,必须切换到主线程——@Published的通知必须在主线程发送,否则SwiftUI无法正确刷新:public func setup() { DispatchQueue.main.async { self.initializeListItems() self.initializeRealTimeToggle() } }
内容的提问来源于stack exchange,提问作者SpaceDust__

