ObservableDefaults
A comprehensive solution integrating SwiftUI + Observation + UserDefaults + iCloud Key-Value Store
Install / Use
/learn @fatbobman/ObservableDefaultsREADME
ObservableDefaults
English | 中文
Seamless SwiftUI + Observation + UserDefaults + iCloud Key-Value Store integration
ObservableDefaults is a comprehensive Swift library that seamlessly integrates both UserDefaults and NSUbiquitousKeyValueStore (iCloud Key-Value Storage) with SwiftUI's Observation framework. It provides two powerful macros - @ObservableDefaults for local UserDefaults management and @ObservableCloud for cloud-synchronized data storage - that simplify data persistence by automatically associating declared properties with their respective storage systems. This enables precise and efficient responsiveness to data changes, whether they originate from within the app, externally, or across multiple devices.
Motivation
Managing multiple UserDefaults keys and cloud-synchronized data in SwiftUI can lead to bloated code and increase the risk of errors. While @AppStorage simplifies handling single UserDefaults keys, it doesn't scale well for multiple keys, lacks cloud synchronization capabilities, or offer precise view updates. With the introduction of the Observation framework, there's a need for a comprehensive solution that efficiently bridges both local and cloud storage with SwiftUI's state management.
ObservableDefaults was created to address these challenges by providing a complete data persistence solution. It leverages macros to reduce boilerplate code and ensures that your SwiftUI views respond accurately to changes in both UserDefaults and iCloud data.
For an in-depth discussion on the limitations of @AppStorage and the motivation behind ObservableDefaults, you can read the full article on my blog.
Don't miss out on the latest updates and excellent articles about Swift, SwiftUI, Core Data, and SwiftData. Subscribe to Fatbobman's Swift Weekly and receive weekly insights and valuable content directly to your inbox.
Features
- Dual Storage Support: Seamless integration with both
UserDefaultsandNSUbiquitousKeyValueStore(iCloud) - SwiftUI Observation: Full integration with the SwiftUI Observation framework
- Automatic Synchronization: Properties automatically sync with their respective storage systems
- Cross-Device Sync: Cloud-backed properties automatically synchronize across user's devices
- Precise Notifications: Property-level change notifications, reducing unnecessary view updates
- Development Mode: Testing support without CloudKit container requirements
- Customizable Behavior: Fine-grained control through additional macros and parameters
- Custom Keys and Prefixes: Support for property-specific storage keys and global prefixes
- Codable Support: Complex data persistence for both local and cloud storage
- Optional Type Support: Full support for Optional properties with nil values
Installation
You can add ObservableDefaults to your project using Swift Package Manager:
- In Xcode, go to File > Add Packages...
- Enter the repository URL:
https://github.com/fatbobman/ObservableDefaults - Select the package and add it to your project.
Usage
UserDefaults Integration with @ObservableDefaults
After importing ObservableDefaults, you can annotate your class with @ObservableDefaults to automatically manage UserDefaults synchronization:
import ObservableDefaults
@ObservableDefaults
class Settings {
var name: String = "Fatbobman"
var age: Int = 20
var nickname: String? = nil // Optional support
}
https://github.com/user-attachments/assets/469d55e8-7468-44ac-b591-804c40815724
This macro automatically:
- Associates the
nameandageproperties withUserDefaultskeys. - Listens for external changes to these keys and updates the properties accordingly.
- Notifies SwiftUI views of changes precisely, avoiding unnecessary redraws.
Cloud Storage Integration with @ObservableCloud
For cloud-synchronized data that automatically syncs across devices, use the @ObservableCloud macro:
import ObservableDefaults
@ObservableCloud
class CloudSettings {
var number = 1
var color: Colors = .red
var style: FontStyle = .style1
var cloudName: String? = nil // Optional support
}
https://github.com/user-attachments/assets/7e8dcf6b-3c8f-4bd3-8083-ff3c4a6bd6b0
This macro automatically:
- Associates properties with
NSUbiquitousKeyValueStorefor iCloud synchronization - Listens for external changes from other devices and updates properties accordingly
- Provides the same precise SwiftUI observation as
@ObservableDefaults - Supports development mode for testing without CloudKit container setup
Using in SwiftUI Views
Both @ObservableDefaults and @ObservableCloud classes work identically in SwiftUI views:
import SwiftUI
struct ContentView: View {
@State var settings = Settings() // UserDefaults-backed
@State var cloudSettings = CloudSettings() // iCloud-backed
var body: some View {
VStack {
// Local settings
Text("Name: \(settings.name)")
TextField("Enter name", text: $settings.name)
// Cloud-synchronized settings
Text("Username: \(cloudSettings.username)")
TextField("Enter username", text: $cloudSettings.username)
}
.padding()
}
}
Customizing Behavior with Additional Macros
For @ObservableDefaults (UserDefaults)
The library provides additional macros for finer control:
@ObservableOnly: The property is observable but not stored inUserDefaults.@Ignore: The property is neither observable nor stored inUserDefaults.@DefaultsKey: Specifies a customUserDefaultskey for the property.@DefaultsBacked: The property is stored inUserDefaultsand observable.@DefaultsBackeddoes not supportwillSet/didSet.
@ObservableDefaults
public class LocalSettings {
@DefaultsKey(userDefaultsKey: "firstName")
public var name: String = "fat"
public var age = 109 // Automatically backed by UserDefaults
@ObservableOnly
public var height = 190 // Observable only, not persisted
@Ignore
public var weight = 10 // Neither observable nor persisted
}
For @ObservableCloud (iCloud Storage)
Similar macro support with cloud-specific options:
@ObservableOnly: The property is observable but not stored inNSUbiquitousKeyValueStore.@Ignore: The property is neither observable nor stored.@CloudKey: Specifies a customNSUbiquitousKeyValueStorekey for the property.@CloudBacked: The property is stored inNSUbiquitousKeyValueStoreand observable.@CloudBackeddoes not supportwillSet/didSet.
@ObservableCloud
public class CloudSettings {
@CloudKey(keyValueStoreKey: "user_display_name")
public var username: String = "Fatbobman"
public var theme: String = "light" // Automatically cloud-backed
@ObservableOnly
public var localCache: String = "" // Observable only, not synced to cloud
@Ignore
public var temporaryData: String = "" // Neither observable nor persisted
}
Initializer and Parameters
@ObservableDefaults Parameters
If all properties have default values, you can use the automatically generated initializer:
public init(
userDefaults: UserDefaults? = nil,
ignoreExternalChanges: Bool? = nil,
prefix: String? = nil
)
Parameters:
userDefaults: TheUserDefaultsinstance to use (default is.standard).ignoreExternalChanges: Iftrue, the instance ignores externalUserDefaultschanges (default isfalse).prefix: A prefix for allUserDefaultskeys associated with this class.
@ObservableCloud Parameters
The cloud version provides similar initialization options:
public init(
prefix: String? = nil,
syncImmediately: Bool = false,
developmentMode: Bool = false
)
Parameters:
prefix: A prefix for allNSUbiquitousKeyValueStorekeys.syncImmediately: Iftrue, forces immediate synchronization after each change.developmentMode: Iftrue, uses memory storage instead of iCloud for testing.
Example Usage
// UserDefaults-backed settings
@State var settings = Settings(
userDefaults: .standard,
ignoreExternalChanges: false,
prefix: "myApp_"
)
// Cloud-backed settings
@State var cloudSettings = CloudSettings(
prefix: "myApp_",
syncImmediately: true,
developmentMode: false
)
Macro Parameters
@ObservableDefaults Macro Parameters
You can set parameters directly in the @ObservableDefaults macro:
userDefaults: TheUserDefaultsinstance to use.ignoreExternalChanges: Whether to ignore external changes.prefix: A prefix forUserDefaultskeys.autoInit: Whether to automatically generate the initializer (default istrue).observeFirst: Observation priority mode (default isfalse).limitToInstance: Whether to limit observations to the specific UserDefaults instance (default istrue). Set tofalsefor App Group cross-process synchronization.
@ObservableDefaults(autoInit: false, ignoreExternalChanges: true, prefix: "myApp_")
c
Related Skills
node-connect
340.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
84.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
340.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
84.2kCommit, push, and open a PR
