SwiftUI 中的 @AppStorage — 是用于 UserDefaults 的 property wrapper,能自动将值与 UI 同步。当通过 @AppStorage 声明的属性发生变更时,新值立即保存到 UserDefaults 中,当 UserDefaults 从外部(例如小组件或扩展)发生变更时,View 自动重绘。据 Apple Developer Documentation (2025),@AppStorage 支持 String、Int、Double、Bool、Data、URL 及其可选版本,无需手动观察代码即可实现用户设置的响应式存储。
主要内容
@AppStorage 是苹果在 iOS 14 中引入的 property wrapper,将 View 的属性与 UserDefaults 中的键关联。读取属性时,SwiftUI 根据指定的键从 UserDefaults 加载值。写入时 — 保存新值并通知 View 需要重绘。
在 @AppStorage 出现之前,开发人员必须在 onAppear 中手动读取 UserDefaults,订阅 UserDefaults.didChangeNotification 通知,并在变更时更新 @State。@AppStorage 自动化了整个循环:一行声明取代了 15–20 行样板代码。此外,@AppStorage 提供双向同步 — 如果 UserDefaults 值从另一进程(例如 App Extension 或 Widget)发生变更,View 仍会接收到更新。
从架构上看,@AppStorage 作为 DynamicProperty 实现,这使得 SwiftUI 能够跟踪依赖关系并在被观察的值变更时重绘 View。这使它成为存储用户设置的理想选择:界面语言、功能开启/关闭、最后选择的标签页、用户名。
尽管 @AppStorage 在底层使用 UserDefaults,但存储方法根本不同。UserDefaults 是低级 API,需要手动管理读取、写入和变更通知。@AppStorage 是 SwiftUI 抽象,开箱即用提供响应式行为。
UserDefaults 适合一次性操作:应用启动时加载设置、写入分析数据、缓存 token。@AppStorage — 适合需要响应式更新 UI的设置:主题开关、语言选择、界面状态保存。在 View 内直接使用 UserDefaults 是反模式,因为没有额外订阅,View 无法知道变更。
| 参数 | @AppStorage | UserDefaults |
|---|---|---|
| 响应性 | 自动 | 需要订阅通知 |
| 样板代码 | 每个属性 1 行 | 每个属性 15–20 行 |
| 类型 | String、Int、Double、Bool、Data、URL | 所有类型 + 归档对象 |
| 自定义类型 | 通过 RawRepresentable | 通过 NSKeyedArchiver |
| App Extension | 自动同步 | 手动订阅 |
对于简单的响应式 UI 设置,@AppStorage 是更佳选择。对于复杂数据(数组、字典、自定义对象),请使用 UserDefaults 与 @State 和手动变更订阅的组合,或转向 SwiftData / Core Data 进行结构化存储。
@AppStorage 支持 UserDefaults 可以直接序列化的标准类型:String、Int、Double、Bool、Data、URL。每种类型都有可选版本(String?、Int?、Double?、Bool?、Data?、URL?),可以区分“未设置”和“空值”。
对于符合 RawRepresentable 协议的自定义类型,@AppStorage 也自动工作。如果枚举的 rawValue 类型为 String 或 Int,可以直接使用:@AppStorage("theme") var theme: AppTheme = .system。SwiftUI 通过 rawValue 自动序列化/反序列化值。
enum AppTheme: String {
case system, light, dark
}
struct SettingsView: View {
@AppStorage("username") var username: String = "Guest"
@AppStorage("launchCount") var launchCount: Int = 0
@AppStorage("isDarkMode") var isDarkMode: Bool = false
@AppStorage("appTheme") var theme: AppTheme = .system
@AppStorage("lastOpened") var lastOpened: Date? = nil
var body: some View {
Form {
TextField("Username", text: $username)
Toggle("Dark mode", isOn: $isDarkMode)
Text("已启动 \(launchCount) 次")
}
}
}
示例中使用了不同类型的 @AppStorage:默认值为 “Guest” 的 String、用于启动次数计数的 Int、用于深色主题的 Bool、rawValue 类型为 String 的 AppTheme 枚举以及可选的 Date?。每个属性都绑定到作为第一个参数指定的 UserDefaults 键。如果首次启动时键不存在,则使用默认值。
@AppStorage 的主要优势之一 — 自动观察来自任何源的 UserDefaults 变更。如果 App Extension 或 Widget 修改了值,父应用程序中的 @AppStorage 会接收通知并重绘 View。这通过 KVO(Key-Value Observing)机制实现,@AppStorage 自动将其设置在 UserDefaults.didChangeNotification 上。
在实践中,这意味着如果用户在小组件中改变设置(例如启用深色主题),应用程序立即捕获这一变更。主应用程序与 Share Extension、Watch App 或 Today Widget 之间的同步也类似工作。开发人员无需编写 进程间数据交换 代码 — @AppStorage 自动完成。
struct ThemeSettingView: View {
@AppStorage("isDarkMode") var isDarkMode: Bool = false
var body: some View {
VStack {
Toggle("Dark Mode", isOn: $isDarkMode)
.onChange(of: isDarkMode) { oldValue, newValue in
print("深色模式已更改为 \(newValue)")
}
}
}
}
Toggle 通过 @AppStorage 与 $isDarkMode 连接。切换时,值自动保存到 UserDefaults 中的 “isDarkMode” 键下。.onChange 修饰符可在变更时执行副作用 — 例如发送分析数据或更新其他屏幕的 UI。如果小组件修改了相同的键,@AppStorage 也会触发 onChange,保证状态一致性。
让我们查看一个完整的应用设置屏幕,它使用 @AppStorage 存储所有配置。表单包含不同类型设置的部分:文本字段、开关、计数器 — 所有值自动保存到 UserDefaults。
struct AppSettingsView: View {
@AppStorage("displayName") var displayName = ""
@AppStorage("notificationsEnabled") var notificationsEnabled = true
@AppStorage("maxResults") var maxResults = 25
@AppStorage("selectedTab") var selectedTab = "home"
var body: some View {
NavigationStack {
Form {
Section(header: Text("个人资料")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("偏好设置")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("重置设置") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
表单包含四个不同类型的 @AppStorage 属性:属性名称的 String、通知的 Bool、结果数量的 Int 和所选标签页的 String。所有控件通过 Binding 与属性连接($displayName、$notificationsEnabled 等)。“Reset settings” 按钮重置所有 UserDefaults,删除应用程序域 — 之后 @AppStorage 自动返回默认值。
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "系统"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "蓝色"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("系统").tag("system")
Text("浅色").tag("浅色")
Text("深色").tag("深色")
}
Picker("Accent color", selection: $widgetColor) {
Text("蓝色").tag("blue")
Text("绿色").tag("绿色")
Text("红色").tag("红色")
}
}
}
}
对于 App Group(应用程序与扩展之间的共享存储),@AppStorage 接受 store 参数:UserDefaults(suiteName:)。值存储在主应用程序、Widget、Watch App 及同一组的其他扩展均可访问的共享容器中。Widget 可以读取这些设置,并在应用程序变更时通过 UserDefaults 观察机制自动更新。
常见问题
@State 仅在内存中存储值,应用重启时会重置。@AppStorage 将值保存在 UserDefaults 中,并在下次启动时恢复。请使用 @State 处理临时屏幕数据,@AppStorage 处理需要在重启后保留的设置。
可以,只要枚举实现了 RawRepresentable 协议,rawValue 类型为 String 或 Int。示例:@AppStorage("theme") var theme: AppTheme = .system。SwiftUI 通过 rawValue 自动序列化枚举并在加载时恢复。
对于标准存储,调用 UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!),或为特定键调用 removeObject(forKey:)。清除后,所有 @AppStorage 属性将返回声明中指定的默认值。
可以,为了在应用程序和扩展之间同步,请使用 App Group:@AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!)。Widget、Share Extension 和 Watch App 可以读写相同的 UserDefaults,变更自动被跟踪。
@AppStorage 使用 UserDefaults,它专为小量数据设计:设置、token、计数器。建议限制为每个应用程序不超过 100 KB。对于结构化或大量数据(对象数组、多媒体文件),请使用 SwiftData、Core Data 或文件系统。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。