SwiftUI视图模型内Binding属性失效问题咨询
1. @Published var toggleViewModel 绑定失效原因
ToggleViewModel是值类型(结构体),ContentViewModel里的@Published var toggleViewModel只有在整个结构体实例被替换时,才会通知SwiftUI更新视图。
切换Toggle时,DataManager.isOn确实会更新(打印日志能验证),但toggleViewModel这个结构体实例本身并没有被替换——它内部的@Binding var isOn虽然已经指向了更新后的DataManager.isOn,但SwiftUI没收到ContentViewModel的更新通知,导致ToggleWrapperView不会重新渲染,视图上的Text(viewModel.isOn)就一直显示旧值。
说白了就是:Binding的源值变了,但承载Binding的结构体没触发视图刷新,所以界面显示没跟上。
2. var isOnBinding: Binding<Bool>? 绑定失效原因
这个自定义Binding有两个致命问题:
privateIsOnForBinding不是@Published属性,它的值变化时,ContentViewModel不会发送任何视图更新通知,SwiftUI完全不知道要刷新界面;- 自定义Binding的getter只返回
privateIsOnForBinding的当前值,但这个值的变化没有任何机制同步给视图,导致界面显示永远停留在初始状态。
针对第一种场景(@Published结构体绑定)
我们需要让ToggleWrapperView能感知到DataManager.isOn的变化,而不是依赖ContentViewModel的@Published结构体更新,有两种可行方式:
方式一:把ToggleViewModel改成ObservableObject
让它持有DataManager的引用,直接观察数据源变化:
// 修改ToggleViewModel为可观察对象 class ToggleViewModel: ObservableObject { @ObservedObject var dataManager: DataManager let title: String // 直接返回DataManager的Binding var isOnBinding: Binding<Bool> { $dataManager.isOn } init(dataManager: DataManager, title: String) { self.dataManager = dataManager self.title = title } } // ContentViewModel中初始化调整 class ContentViewModel: ObservableObject { @ObservedObject var dataManager = DataManager() @Published var toggleViewModel: ToggleViewModel! init() { toggleViewModel = ToggleViewModel(dataManager: dataManager, title: "Binding from DataManager") } } // ToggleWrapperView改为观察ToggleViewModel struct ToggleWrapperView: View { @ObservedObject var viewModel: ToggleViewModel var body: some View { VStack { Toggle(isOn: viewModel.isOnBinding, label: { Text(viewModel.title + " \(viewModel.dataManager.isOn)") }) } } }
这样DataManager.isOn变化时,ToggleViewModel会收到通知,直接触发ToggleWrapperView刷新。
方式二:监听DataManager变化,手动替换ToggleViewModel实例
如果坚持保留ToggleViewModel为结构体,可以让ContentViewModel监听DataManager的变化,每次数据源更新就重新创建toggleViewModel:
class ContentViewModel: ObservableObject { @ObservedObject var dataManager = DataManager() @Published var toggleViewModel: ToggleViewModel! private var cancellables = Set<AnyCancellable>() init() { updateToggleViewModel() // 监听DataManager的变化,每次更新都替换toggleViewModel实例 dataManager.objectWillChange .sink { [weak self] _ in self?.updateToggleViewModel() } .store(in: &cancellables) } private func updateToggleViewModel() { toggleViewModel = ToggleViewModel(isOn: $dataManager.isOn, title: "Binding from DataManager") } }
每次DataManager更新,toggleViewModel都会被替换成新的结构体实例,触发@Published的更新通知,让ToggleWrapperView重新渲染。
针对第二种场景(自定义Binding)
只需要把privateIsOnForBinding改成@Published属性,让它的变化能触发视图更新:
class ContentViewModel: ObservableObject { @Published var privateIsOnForBinding: Bool = false // 去掉可选型,直接返回Binding var isOnBinding: Binding<Bool> { Binding { self.privateIsOnForBinding } set: { updatedValue in self.privateIsOnForBinding = updatedValue } } }
现在privateIsOnForBinding变化时,ContentViewModel会自动发送更新通知,ToggleWrapperView就能同步显示最新值了。
内容的提问来源于stack exchange,提问作者Ruslan

