SwiftUI中使用UserDefaults触发视图更新警告,如何修复?
问题解决:"Publishing changes from within view updates is not allowed" 警告
问题根源
defaults.register(defaults:) 会触发 UserDefaults.didChangeNotification,而你的 ViewModel 在初始化时先注册默认值再订阅该通知,导致通知事件刚好在视图更新周期内被处理,触发了 SwiftUI 的规则警告——禁止在视图更新过程中发布状态变更。同时代码还存在两处细节错误:View 中引用了不存在的 sliderValue 属性,以及手动管理 objectWillChange 容易引发的生命周期问题。
修改方案
1. 重构 ViewModel,改用 @Published 自动管理状态发布
移除手动创建的 objectWillChange 和 Cancellable,用 @Published 包装属性,让 SwiftUI 自动处理状态变更通知,同时修正 UserDefaults 的读写逻辑:
class SettingsViewModel: ObservableObject { private let defaults: UserDefaults private enum Keys { static let channel1Value = "channel1_value" static let channel2Value = "channel2_value" } @Published var channel1Value: Double { didSet { defaults.set(channel1Value, forKey: Keys.channel1Value) } } @Published var channel2Value: Double { didSet { defaults.set(channel2Value, forKey: Keys.channel2Value) } } init(defaults: UserDefaults = .standard) { self.defaults = defaults // 先注册默认值,再读取初始值 defaults.register(defaults: [ Keys.channel1Value: 95, Keys.channel2Value: 95 ]) // 从 UserDefaults 加载初始值 channel1Value = defaults.double(forKey: Keys.channel1Value) channel2Value = defaults.double(forKey: Keys.channel2Value) } func resetDefaultsValue() { channel1Value = 95 channel2Value = 95 } }
2. 修正 View 中的属性引用错误
将 View 中不存在的 sliderValue 改为对应属性(如 channel1Value),同时修正 @StateObject 的类型声明:
struct SliderView: View { @StateObject var vm = SettingsViewModel() var body: some View { HStack { Text("Channel 1") .frame(width: 100, alignment: .leading) Slider(value: $vm.channel1Value, in: 0...100, step: 1) { Text("Adjust Channel 1") } .padding(.trailing, 10) } } }
3. 如需监听全局 UserDefaults 变更的补充方案
如果需要监听 App 其他模块对这些 UserDefaults 值的修改,可以保留通知订阅,但要确保在主线程异步处理,避免触发视图更新周期冲突:
class SettingsViewModel: ObservableObject { private let defaults: UserDefaults private var cancellable: AnyCancellable? private enum Keys { static let channel1Value = "channel1_value" static let channel2Value = "channel2_value" } @Published var channel1Value: Double { didSet { defaults.set(channel1Value, forKey: Keys.channel1Value) } } @Published var channel2Value: Double { didSet { defaults.set(channel2Value, forKey: Keys.channel2Value) } } init(defaults: UserDefaults = .standard) { self.defaults = defaults defaults.register(defaults: [ Keys.channel1Value: 95, Keys.channel2Value: 95 ]) channel1Value = defaults.double(forKey: Keys.channel1Value) channel2Value = defaults.double(forKey: Keys.channel2Value) // 异步处理通知,避免视图更新周期内发布变更 cancellable = NotificationCenter.default .publisher(for: UserDefaults.didChangeNotification) .receive(on: DispatchQueue.main) .sink { [weak self] _ in guard let self = self else { return } self.channel1Value = self.defaults.double(forKey: Keys.channel1Value) self.channel2Value = self.defaults.double(forKey: Keys.channel2Value) } } func resetDefaultsValue() { channel1Value = 95 channel2Value = 95 } }
内容的提问来源于stack exchange,提问作者Jameel
相关产品推荐
相关产品推荐

