SwiftUI实现Picker读写UserDefaults并兼容iOS14与iOS15方案咨询
兼容iOS14/iOS15的正确实现方案
问题根源
- 第一个方案的@State初始值仅在View首次实例化时读取一次UserDefaults,后续页面重新进入时不会主动更新@State的值,只有App重启View完全销毁重建时才会读取最新值,导致退出重进显示旧值。
- 第二个方案受iOS14系统特性限制,Form内嵌的Picker跳转选择页返回时,父页面
onAppear回调优先级高于onChange,导致还没写入新值就先读了旧的UserDefaults数据,触发值回滚。
最优方案:使用@AppStorage属性包装器
@AppStorage是iOS14+系统内置的UserDefaults封装属性包装器,自带响应式更新能力,自动完成读写同步,无需手动处理读取、存储、监听逻辑,完美规避上述两个问题,代码实现极简:
struct SettingsView: View { // 直接用@AppStorage绑定UserDefaults,指定key和默认值即可 @AppStorage("vocalRange") var rangeSelection: Int = 0 let rangeOptions = ["Soprano", "Mezzo-Soprano/Alto", "Extended Range"] var body: some View { VStack{ Form { Section { Picker(selection: $rangeSelection, label: Text("Range")) { ForEach(0 ..< rangeOptions.count, id: \.self) { range in Text(rangeOptions[range]) .tag(range) } } // 无需额外添加onChange、onAppear处理逻辑 } } } .navigationTitle("Settings") .navigationBarTitleDisplayMode(.inline) } }
备选方案:自定义Binding(无需依赖@AppStorage)
如果有特殊场景不想使用@AppStorage,可以通过自定义Binding直接关联UserDefaults的读写,同样可以规避生命周期回调的顺序问题:
struct SettingsView: View { let rangeOptions = ["Soprano", "Mezzo-Soprano/Alto", "Extended Range"] var body: some View { VStack{ Form { Section { Picker(selection: Binding( get: { UserDefaults.standard.integer(forKey: "vocalRange") }, set: { UserDefaults.standard.setValue($0, forKey: "vocalRange") } ), label: Text("Range")) { ForEach(0 ..< rangeOptions.count, id: \.self) { range in Text(rangeOptions[range]) .tag(range) } } } } } .navigationTitle("Settings") .navigationBarTitleDisplayMode(.inline) } }
两种方案都可以在iOS14、iOS15上正常运行,不会出现值回滚、退出重进不更新的问题。
内容的提问来源于stack exchange,提问作者Muindor
相关产品推荐
相关产品推荐

