SkillAgentSearch skills...

ObservableDefaults

A comprehensive solution integrating SwiftUI + Observation + UserDefaults + iCloud Key-Value Store

Install / Use

/learn @fatbobman/ObservableDefaults

README

ObservableDefaults

English | 中文

Swift 6 iOS macOS watchOS visionOS tvOS License: MIT Ask DeepWiki

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 UserDefaults and NSUbiquitousKeyValueStore (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:

  1. In Xcode, go to File > Add Packages...
  2. Enter the repository URL: https://github.com/fatbobman/ObservableDefaults
  3. 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 name and age properties with UserDefaults keys.
  • 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

Demo Code

This macro automatically:

  • Associates properties with NSUbiquitousKeyValueStore for 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 in UserDefaults.
  • @Ignore: The property is neither observable nor stored in UserDefaults.
  • @DefaultsKey: Specifies a custom UserDefaults key for the property.
  • @DefaultsBacked: The property is stored in UserDefaults and observable.
  • @DefaultsBacked does not support willSet / 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 in NSUbiquitousKeyValueStore.
  • @Ignore: The property is neither observable nor stored.
  • @CloudKey: Specifies a custom NSUbiquitousKeyValueStore key for the property.
  • @CloudBacked: The property is stored in NSUbiquitousKeyValueStore and observable.
  • @CloudBacked does not support willSet / 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: The UserDefaults instance to use (default is .standard).
  • ignoreExternalChanges: If true, the instance ignores external UserDefaults changes (default is false).
  • prefix: A prefix for all UserDefaults keys 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 all NSUbiquitousKeyValueStore keys.
  • syncImmediately: If true, forces immediate synchronization after each change.
  • developmentMode: If true, 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: The UserDefaults instance to use.
  • ignoreExternalChanges: Whether to ignore external changes.
  • prefix: A prefix for UserDefaults keys.
  • autoInit: Whether to automatically generate the initializer (default is true).
  • observeFirst: Observation priority mode (default is false).
  • limitToInstance: Whether to limit observations to the specific UserDefaults instance (default is true). Set to false for App Group cross-process synchronization.
@ObservableDefaults(autoInit: false, ignoreExternalChanges: true, prefix: "myApp_")
c

Related Skills

View on GitHub
GitHub Stars385
CategoryDevelopment
Updated9d ago
Forks13

Languages

Swift

Security Score

100/100

Audited on Mar 21, 2026

No findings