在iOS应用中,@AppStorage仅保存到设备本地,@AppStorage默认是与UserDefaults关联的,但可以使用 NSUbiquitousKeyValueStore 将数据同步到iCloud。
1、配置 iCloud
首先,确保在 Xcode 项目中启用了 iCloud 并配置了 iCloud 容器。
打开 Xcode 项目。

转到 Signing & Capabilities 选项卡。

启用 iCloud,确保勾选了 iCloud Key-Value Store。

如果之前没有接触过iCloud,相关教程可以查看《SwiftData数据同步到iCloud》
2、使用 NSUbiquitousKeyValueStore 实现同步
NSUbiquitousKeyValueStore 可以在不同设备之间同步数据。需要手动同步 CurrencySymbol。
import SwiftUI
@main
struct ContentView: View {
@AppStorage("CurrencySymbol") var CurrencySymbol: String = "USD" {
didSet {
// 每次 CurrencySymbol 更新时,同步到 iCloud
syncToiCloud()
}
}
init() {
// 初始化时从 iCloud 获取值,但只有在 iCloud 存在值时才覆盖本地值
loadFromiCloud()
}
func syncToiCloud() {
// 将 CurrencySymbol 同步到 iCloud
let store = NSUbiquitousKeyValueStore.default
store.set(CurrencySymbol, forKey: "CurrencySymbol")
store.synchronize()
}
func loadFromiCloud() {
// 从 iCloud 加载 CurrencySymbol 的值
let store = NSUbiquitousKeyValueStore.default
if let storedSymbol = store.string(forKey: "CurrencySymbol"), storedSymbol != "" {
// 只有当 iCloud 存储值非空时才覆盖本地值
CurrencySymbol = storedSymbol
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
在首次显示视图时,如果 NSUbiquitousKeyValueStore 中没有存储 CurrencySymbol,它会返回 nil,这样就会导致本地的 CurrencySymbol 被同步成空值。为了避免这种情况,可以在加载 iCloud 数据时检查 NSUbiquitousKeyValueStore 中是否有数据,如果没有,则保留本地已有的值。
3、注意事项
NSUbiquitousKeyValueStore 会在设备之间同步数据,但同步不是即时的。可以使用 synchronize() 强制同步,但 iCloud 同步可能会有延迟。
确保 CurrencySymbol 是一个基本类型(如字符串、整数、布尔值等),因为 NSUbiquitousKeyValueStore 仅支持这些类型。
总结
在实际应用中,视图可能会重复调用某个@AppStorage变量,因此还可以将@AppStorage变量统一管理到一个类中。
在下面的代码中,创建了一个AppStorageManager类,并通过单例模式统一管理。
import SwiftUI
import Observation
@Observable
class AppStorageManager {
static let shared = AppStorageManager() // 全局单例
private init() {
// 初始化时同步本地存储
loadUserDefault()
// 从iCloud读取数据
loadFromiCloud()
}
var pageSteps: Int = 1 // 视图步骤
var isBiometricEnabled: Bool = false // 密码保护
// 从UserDefaults加载数据
private func loadUserDefault() {
pageSteps = UserDefaults.standard.integer(forKey: "pageSteps") // 视图步骤
isBiometricEnabled = UserDefaults.standard.bool(forKey: "isBiometricEnabled") // 密码保护
}
/// 从 iCloud 读取数据
private func loadFromiCloud() {
let store = NSUbiquitousKeyValueStore.default
print("从iCloud读取数据")
// 读取整数值
if let storedPageSteps = store.object(forKey: "pageSteps") as? Int {
pageSteps = storedPageSteps
}
// 读取布尔值
if store.object(forKey: "isBiometricEnabled") != nil {
isBiometricEnabled = store.bool(forKey: "isBiometricEnabled")
}
}
/// 数据变化时,**同步到 iCloud**
private func syncToiCloud() {
let store = NSUbiquitousKeyValueStore.default
store.set(pageSteps, forKey: "pageSteps")
store.set(isBiometricEnabled, forKey: "isBiometricEnabled")
store.synchronize() // 强制触发数据同步
}
}
在视图中,直接使用共享实例获取属性。
struct Home: View {
var appStorage = AppStorageManager.shared // 共享实例
var body: some View {
// 使用 appStorage
Text("\(appStorage.pageSteps)")
}
}
如果涉及到UI组件绑定,则使用Binding:
CreatePiggyBankPage1(pageSteps: Binding(
get: { appStorage.pageSteps },
set: { appStorage.pageSteps = $0 }
)
还可以通过NotificationCenter进一步监听iCloud数据变化和应用生命周期变化,从而实现数据的同步。
/// 监听 iCloud 变化,同步到本地
private func observeiCloudChanges() {
NotificationCenter.default.addObserver(
self,
selector: #selector(iCloudDidUpdate),
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: NSUbiquitousKeyValueStore.default
)
}
/// iCloud 数据变化时,更新本地数据
@objc private func iCloudDidUpdate(notification: Notification) {
print("iCloud数据发生变化,更新本地数据")
DispatchQueue.main.async {
self.loadFromiCloud()
}
}
/// 监听应用生命周期事件
private func observeAppLifecycle() {
NotificationCenter.default.addObserver(
self,
selector: #selector(appWillResignActive),
name: UIApplication.willResignActiveNotification,
object: nil
)
}
/// 当应用进入后台时,将数据同步到 iCloud
@objc private func appWillResignActive() {
print("应用进入后台,将本地数据同步到iCloud")
syncToiCloud()
}
/// 防止内存泄漏
deinit {
NotificationCenter.default.removeObserver(self, name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default)
NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
}
在store中读取Int、Double的格式也有所区别:
// 读取整数值
if let storedPageSteps = store.object(forKey: "pageSteps") as? Int {
pageSteps = storedPageSteps
}
// 读取双精度值
if store.object(forKey: "reminderTime") != nil {
reminderTime = store.double(forKey: "reminderTime")
}
// 读取布尔值
if store.object(forKey: "isBiometricEnabled") != nil {
isBiometricEnabled = store.bool(forKey: "isBiometricEnabled")
}
// 读取字符串值
if let storedBackgroundImage = store.string(forKey: "BackgroundImage") {
BackgroundImage = storedBackgroundImage
}
因此,需要根据数据类型进度读取。
相关文章
1、SwiftData数据同步到iCloud:https://fangjunyu.com/2024/11/10/swiftdata%e6%95%b0%e6%8d%ae%e5%90%8c%e6%ad%a5%e5%88%b0icloud/
2、iOS通知机制NotificationCenter:https://fangjunyu.com/2025/03/01/ios%e9%80%9a%e7%9f%a5%e6%9c%ba%e5%88%b6notificationcenter/
完整代码
import SwiftUI
import Observation
@Observable
class AppStorageManager {
static let shared = AppStorageManager() // 全局单例
private init() {
// 初始化时同步本地存储
loadUserDefault()
// 从iCloud读取数据
loadFromiCloud()
// 监听 iCloud 变化,同步到本地
observeiCloudChanges()
// 监听应用进入后台事件
observeAppLifecycle()
}
var pageSteps: Int = 1 // 视图步骤
var isBiometricEnabled: Bool = false // 密码保护
var BackgroundImage = "" // 背景照片
var LoopAnimation = "Home0" // Lottie动画
var isLoopAnimation = false // 循环动画
var isSilentMode = false // 静默模式
var CurrencySymbol = "USD" // 货币符号
var SwitchTopStyle: Bool = false // 存钱罐首页显示样式,为true则显示已存入的金额
var RatingClicks: Int = 0 // 请求评分
var isInAppPurchase = false /// 内购完成后,设置为true,@AppStorage("20240523")
var isShowAboutUs = true // false表示隐藏
var isShowInAppPurchase = true // 控制内购按钮,false表示隐藏
var isShowThanks = true // 控制鸣谢页面,false表示隐藏
var isModelConfigManager = true // ModelConfig配置
var isReminderTime = false // 提醒时间,设置提醒时间为true,否则为false
var reminderTime: Double = Date().timeIntervalSince1970 // 存储用户设定的提醒时间
// 从UserDefaults加载数据
private func loadUserDefault() {
pageSteps = UserDefaults.standard.integer(forKey: "pageSteps") // 视图步骤
isBiometricEnabled = UserDefaults.standard.bool(forKey: "isBiometricEnabled") // 密码保护
BackgroundImage = UserDefaults.standard.string(forKey: "BackgroundImage") ?? "" // 背景照片
LoopAnimation = UserDefaults.standard.string(forKey: "LoopAnimation") ?? "Home0" // Lottie动画
isLoopAnimation = UserDefaults.standard.bool(forKey: "isLoopAnimation") // 循环动画
isSilentMode = UserDefaults.standard.bool(forKey: "isSilentMode") // 静默模式
CurrencySymbol = UserDefaults.standard.string(forKey: "CurrencySymbol") ?? "USD" // 密码保护
SwitchTopStyle = UserDefaults.standard.bool(forKey: "SwitchTopStyle") // 存钱罐首页显示样式,为true则显示已存入的金额
RatingClicks = UserDefaults.standard.integer(forKey: "RatingClicks") // 请求评分
isInAppPurchase = UserDefaults.standard.bool(forKey: "20240523") /// 内购完成后,设置为true,@AppStorage("20240523")
isShowAboutUs = UserDefaults.standard.bool(forKey: "isShowAboutUs") // false表示隐藏
isShowInAppPurchase = UserDefaults.standard.bool(forKey: "isShowInAppPurchase") // 控制内购按钮,false表示隐藏
isShowThanks = UserDefaults.standard.bool(forKey: "isShowThanks") // 控制鸣谢页面,false表示隐藏
isModelConfigManager = UserDefaults.standard.bool(forKey: "isModelConfigManager") // ModelConfig配置
isReminderTime = UserDefaults.standard.bool(forKey: "isReminderTime") // 提醒时间,设置提醒时间为true,否则为false
reminderTime = UserDefaults.standard.double(forKey: "reminderTime") // 存储用户设定的提醒时间
}
/// 从 iCloud 读取数据
private func loadFromiCloud() {
let store = NSUbiquitousKeyValueStore.default
print("从iCloud读取数据")
// 读取整数值
if let storedPageSteps = store.object(forKey: "pageSteps") as? Int {
pageSteps = storedPageSteps
}
if let storedRatingClicks = store.object(forKey: "RatingClicks") as? Int {
RatingClicks = storedRatingClicks
}
// 读取双精度值
if store.object(forKey: "reminderTime") != nil {
reminderTime = store.double(forKey: "reminderTime")
}
// 读取布尔值
if store.object(forKey: "isBiometricEnabled") != nil {
isBiometricEnabled = store.bool(forKey: "isBiometricEnabled")
}
if store.object(forKey: "isLoopAnimation") != nil {
isLoopAnimation = store.bool(forKey: "isLoopAnimation")
}
if store.object(forKey: "isSilentMode") != nil {
isSilentMode = store.bool(forKey: "isSilentMode")
}
if store.object(forKey: "SwitchTopStyle") != nil {
SwitchTopStyle = store.bool(forKey: "SwitchTopStyle")
}
if store.object(forKey: "20240523") != nil {
isInAppPurchase = store.bool(forKey: "20240523")
}
if store.object(forKey: "isShowAboutUs") != nil {
isShowAboutUs = store.bool(forKey: "isShowAboutUs")
}
if store.object(forKey: "isShowInAppPurchase") != nil {
isShowInAppPurchase = store.bool(forKey: "isShowInAppPurchase")
}
if store.object(forKey: "isShowThanks") != nil {
isShowThanks = store.bool(forKey: "isShowThanks")
}
if store.object(forKey: "isModelConfigManager") != nil {
isModelConfigManager = store.bool(forKey: "isModelConfigManager")
}
if store.object(forKey: "isReminderTime") != nil {
isReminderTime = store.bool(forKey: "isReminderTime")
}
// 读取字符串值
if let storedBackgroundImage = store.string(forKey: "BackgroundImage") {
BackgroundImage = storedBackgroundImage
}
if let storedLoopAnimation = store.string(forKey: "LoopAnimation") {
LoopAnimation = storedLoopAnimation
}
if let storedCurrencySymbol = store.string(forKey: "CurrencySymbol") {
CurrencySymbol = storedCurrencySymbol
}
print("完成 loadFromiCloud 方法的读取")
print("pageSteps: \(pageSteps)")
print("isBiometricEnabled: \(isBiometricEnabled)")
print("BackgroundImage: \(BackgroundImage)")
print("LoopAnimation: \(LoopAnimation)")
print("isLoopAnimation: \(isLoopAnimation)")
print("isSilentMode: \(isSilentMode)")
print("CurrencySymbol: \(CurrencySymbol)")
print("SwitchTopStyle: \(SwitchTopStyle)")
print("RatingClicks: \(RatingClicks)")
print("isInAppPurchase: \(isInAppPurchase)")
print("isShowAboutUs: \(isShowAboutUs)")
print("isShowInAppPurchase: \(isShowInAppPurchase)")
print("isShowThanks: \(isShowThanks)")
print("isModelConfigManager: \(isModelConfigManager)")
print("isReminderTime:\(isReminderTime)")
}
/// 数据变化时,**同步到 iCloud**
private func syncToiCloud() {
let store = NSUbiquitousKeyValueStore.default
store.set(pageSteps, forKey: "pageSteps")
store.set(isBiometricEnabled, forKey: "isBiometricEnabled")
store.set(BackgroundImage, forKey: "BackgroundImage")
store.set(LoopAnimation, forKey: "LoopAnimation")
store.set(isLoopAnimation, forKey: "isLoopAnimation")
store.set(isSilentMode, forKey: "isSilentMode")
store.set(CurrencySymbol, forKey: "CurrencySymbol")
store.set(SwitchTopStyle, forKey: "SwitchTopStyle")
store.set(RatingClicks, forKey: "RatingClicks")
store.set(isInAppPurchase, forKey: "20240523")
store.set(isShowAboutUs, forKey: "isShowAboutUs")
store.set(isShowInAppPurchase, forKey: "isShowInAppPurchase")
store.set(isShowThanks, forKey: "isShowThanks")
store.set(isModelConfigManager, forKey: "isModelConfigManager")
store.set(isReminderTime, forKey: "isReminderTime")
store.set(reminderTime, forKey: "reminderTime")
store.synchronize() // 强制触发数据同步
}
/// 监听 iCloud 变化,同步到本地
private func observeiCloudChanges() {
NotificationCenter.default.addObserver(
self,
selector: #selector(iCloudDidUpdate),
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: NSUbiquitousKeyValueStore.default
)
}
/// iCloud 数据变化时,更新本地数据
@objc private func iCloudDidUpdate(notification: Notification) {
print("iCloud数据发生变化,更新本地数据")
DispatchQueue.main.async {
self.loadFromiCloud()
}
}
/// 监听应用生命周期事件
private func observeAppLifecycle() {
NotificationCenter.default.addObserver(
self,
selector: #selector(appWillResignActive),
name: UIApplication.willResignActiveNotification,
object: nil
)
}
/// 当应用进入后台时,将数据同步到 iCloud
@objc private func appWillResignActive() {
print("应用进入后台,将本地数据同步到iCloud")
syncToiCloud()
}
/// 防止内存泄漏
deinit {
NotificationCenter.default.removeObserver(self, name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default)
NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
}
}