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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 21:30:54