使用 User Defaults 存储用户设置信息
了解如何使用苹果提供的 User Defaults 持久化存储轻量级的用户数据,并使用 SwiftUI 的 @AppStorage 属性包装器简化数据存取。
Swift 为应用开发提供了多种本地数据存储方案。如果需要持久化存储较大结构化数据,首先考虑使用 2023 年新推出的 SwiftData 框架,它与 SwiftUI 有非常好的集成,你可以查看我的系列文章:
如果是存储用户的偏好设置、应用配置这种小规模数据,则适合使用 UserDefaults 。本文将详细介绍如何使用 UserDefaults
存储用户设置信息,并探讨一些最佳实践,确保数据存储安全和高效。
什么是 User Defaults
UserDefaults
是 iOS 和 macOS 中用于存储应用小型数据的简单持久化机制。它可以存储一些基本数据类型,如字符串、整数、布尔值、数组和字典等。
UserDefaults
允许我们在应用的多个会话之间保存轻量级的键值对数据,这使得用户的设置信息能够在应用关闭后依然被保留。 因此,非常适合保存用户偏好设置、应用配置、状态标志等不需要复杂数据结构的场景。
适合 User Defaults 的使用场景
一些常见的 UserDefaults 使用场景:
- 保存用户选择的主题(如深色模式或浅色模式)
- 记录用户的语言偏好
- 存储是否显示应用的欢迎提示
- 保存应用的配置信息,例如音量设置、通知开关等
使用 User Defaults 存储数据与读取数据
要将数据存储在 UserDefaults
中,只需使用 set
方法并指定键值对。如下所示:
// 存储用户的首选主题设置
UserDefaults.standard.set("dark", forKey: "userTheme")
// 存储用户的音量设置
UserDefaults.standard.set(0.8, forKey: "volumeLevel")
// 存储是否启用通知
UserDefaults.standard.set(true, forKey: "isNotificationsEnabled")
从 UserDefaults
中读取数据非常简单,通过 object(forKey:)
或者相应的类型方法即可检索数据:
// 获取用户的首选主题
let userTheme = UserDefaults.standard.string(forKey: "userTheme") ?? "light"
// 获取音量设置
let volumeLevel = UserDefaults.standard.double(forKey: "volumeLevel")
// 获取通知设置
let isNotificationsEnabled = UserDefaults.standard.bool(forKey: "isNotificationsEnabled")
UserDefaults 的数据管理方法,和我们之前讲过的 UIPasteboard 非常相似:
使用 User Defaults 存储用户设置案例
为了更好地管理用户设置,通常会创建专门的类来定义和封装这些参数。
为了创建类似上面这样的设置页面,我们需要:
- 创建一个
SettingsManager
类,用于集中管理 UserDefaults 的键值(否则键值多了之后就会混乱)。它封装了与UserDefaults
的交互,确保各个设置项的存储和读取操作简单易用。 - 创建一个
SettingsView
,用户可以通过该视图修改应用的设置。提供了用户界面,用于调整主题、音量和通知设置。每次用户在界面上修改设置时,都会自动更新到UserDefaults
中。
SettingsManager
类
这个类将负责与 UserDefaults
进行交互,并封装对用户设置的读取和存储逻辑。
import Foundation
class SettingsManager {
static let shared = SettingsManager()
private let userThemeKey = "userTheme"
private let volumeLevelKey = "volumeLevel"
private let notificationsEnabledKey = "isNotificationsEnabled"
// 存储用户的首选主题
func setUserTheme(_ theme: String) {
UserDefaults.standard.set(theme, forKey: userThemeKey)
}
// 读取用户的首选主题
func getUserTheme() -> String {
return UserDefaults.standard.string(forKey: userThemeKey) ?? "light" // 默认主题为浅色模式
}
// 存储用户的音量设置
func setVolumeLevel(_ level: Float) {
UserDefaults.standard.set(level, forKey: volumeLevelKey)
}
// 读取用户的音量设置
func getVolumeLevel() -> Float {
return UserDefaults.standard.float(forKey: volumeLevelKey)
}
// 存储通知设置
func setNotificationsEnabled(_ isEnabled: Bool) {
UserDefaults.standard.set(isEnabled, forKey: notificationsEnabledKey)
}
// 读取通知设置
func areNotificationsEnabled() -> Bool {
return UserDefaults.standard.bool(forKey: notificationsEnabledKey)
}
}
SettingsView
视图
SettingsView
提供了一个界面,用户可以通过它来修改应用的设置。我们将使用 SwiftUI 创建这个视图,并通过 SettingsManager
进行数据的保存和读取。
import SwiftUI
struct SettingsView: View {
@State private var selectedTheme = SettingsManager.shared.getUserTheme()
@State private var volumeLevel = SettingsManager.shared.getVolumeLevel()
@State private var notificationsEnabled = SettingsManager.shared.areNotificationsEnabled()
var body: some View {
Form {
Section(header: Text("主题设置")) {
Picker("选择主题", selection: $selectedTheme) {
Text("浅色模式").tag("light")
Text("深色模式").tag("dark")
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: selectedTheme) { newValue in
SettingsManager.shared.setUserTheme(newValue)
}
}
Section(header: Text("音量设置")) {
Slider(value: $volumeLevel, in: 0...1) {
Text("音量")
}
.onChange(of: volumeLevel) { newValue in
SettingsManager.shared.setVolumeLevel(newValue)
}
}
Section(header: Text("通知设置")) {
Toggle("启用通知", isOn: $notificationsEnabled)
.onChange(of: notificationsEnabled) { newValue in
SettingsManager.shared.setNotificationsEnabled(newValue)
}
}
}
.navigationTitle("用户设置")
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}
这个设计的好处是将用户设置的存取逻辑与界面逻辑分离,增强了代码的可维护性和清晰度。
使用 @AppStorage 简化数据存取
@AppStorage 是 SwiftUI 提供的一个属性包装器,用于简化对 UserDefaults 数据的绑定和管理(存取)。它能够直接在 SwiftUI 视图中绑定用户设置,只要存储的值发生变化,就会自动更新视图。它还充当@State
变量,触发视图中的更改。
@AppStorage 只能用来绑定 UserDefaults 中的数据。
使用 @AppStorage 修改实现
在之前的 SettingsView 和 SettingsManager 代码中,我们需要手动存取键值,并在更新时,手动调用 onChange 与 set 方法来存储值:
// 存储用户的首选主题
func setUserTheme(_ theme: String) {
UserDefaults.standard.set(theme, forKey: userThemeKey)
}
// 读取用户的首选主题
func getUserTheme() -> String {
return UserDefaults.standard.string(forKey: userThemeKey) ?? "light" // 默认主题为浅色模式
}
@State private var selectedTheme = SettingsManager.shared.getUserTheme()
.onChange(of: selectedTheme) { newValue in
SettingsManager.shared.setUserTheme(newValue)
}
如果使用 @AppStorage,你无需再手动处理数据更新,SwiftUI 会自动存储数据的更新与存储。@AppStorage
可以在视图中直接使用,视图将在数据变化时自动刷新。因此,我们不再需要 SettingManager 类,只需在 SettingView 中实现即可:
SettingView:
import SwiftUI
struct SettingsView: View {
@AppStorage("userTheme") private var selectedTheme: String = "light"
@AppStorage("volumeLevel") private var volumeLevel: Float = 0.5
@AppStorage("isNotificationsEnabled") private var notificationsEnabled: Bool = false
var body: some View {
Form {
Section(header: Text("主题设置")) {
Picker("选择主题", selection: $selectedTheme) {
Text("浅色模式").tag("light")
Text("深色模式").tag("dark")
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("音量设置")) {
Slider(value: $volumeLevel, in: 0...1) {
Text("音量")
}
}
Section(header: Text("通知设置")) {
Toggle("启用通知", isOn: $notificationsEnabled)
}
}
.navigationTitle("用户设置")
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}
以上代码实现了相同的功能,但相比之前的方式更为简洁高效,代码量大大减少。
什么场景推荐使用 @AppStorage
@AppStorage 本质也是使用 UserDefault 来存储数据,所以一个常见的问题是:我们什么时候应该使用 @AppStorage,什么场景又应该采取之前这种通过类来统一管理的方案?
按照我的经验,@AppStorage
非常适合于你的 App 只有少量的设置项、逻辑简单的场景。当值改变时,SwiftUI 会自动更新 UI,使用起来非常方便。
但如果你需要对设置进行复杂的逻辑处理,或者设置选项较多(例如 10 个以上),则推荐使用类来集中管理设置键值对的存取。