Use UserDefaults in AppKit and UIKit as easily as @AppStorage in SwiftUI, with simple reactivity via didSet, no Combine dependency required.
- Author: Terence J. Grant
- License: MIT License
- Donate: Your donations are appreciated!
@DefaultsStorage is a property wrapper that reflects a UserDefaults value, allowing reactive updates via didSet when values change.
This work-alike for @AppStorage reads from and writes to UserDefaults the same way, making migration to or from SwiftUI straightforward.
- Simple property wrapper for storing and retrieving
UserDefaultsvalues. - Supports
Bool,Int,Double,String,Date, andData.- Arrays of those types, for instance
[Int] - Dictionaries of those types where the "key" is of type
String, for instance[String: Int]
- Arrays of those types, for instance
- Also supports
URLand enums withStringorIntraw values. - Provides
didSet-based reactivity. - Compatible with values stored by
@AppStorage. - Fully tested for reliability.
Add DefaultsStorage to your project with Swift Package Manager in Xcode:
- Go to "File -> Add Package Dependency…"
- Enter the package URL:
https://github.com/tatewake/DefaultsStorage - Use "Dependency Rule -> Exact Version" and set to use release version "1.1.0".
The @DefaultStorage tag will load from UserDefaults on instantiation and save automatically when the value changes. If no value exists in UserDefaults, the initializer will provide a default value.
@DefaultsStorage("name") var name = "User"This syncs automatically on initialization or when modified in code.
Here’s an example using SettingsModel as a struct to leverage didSet reactivity.
struct SettingsModel {
struct Display: Equatable {
enum Address: String {
case first, multiple, last
}
@DefaultsStorage("addressSourcePublic") var addressSourcePublic = false
@DefaultsStorage("addressToDisplay") var addressToDisplay = Address.multiple
}
struct Network: Equatable {
@DefaultsStorage("offlineUpdateFrequency") var offlineUpdateFrequency = 30.0
@DefaultsStorage("onlineUpdateFrequency") var onlineUpdateFrequency = 60.0
}
var display = Display()
var network = Network()
}Now, in a top-level Model class, we use didSet to handle changes in SettingsModel via a delegate:
class Model {
weak var delegate: MyDelegate?
var settings = SettingsModel() {
didSet {
if oldValue.display != settings.display {
delegate?.displaySettingsChanged(display: settings.display)
}
if oldValue.network != settings.network {
delegate?.networkSettingsChanged(network: settings.network)
}
}
}
...
}This is just a simple example, but it shows the straightforward nature of DefaultsStorage.
While @DefaultsStorage is similar to @AppStorage, there are two key differences:
- External changes won’t update in real time, and
- Properties sharing the same key won’t stay in sync.
This is because @DefaultsStorage only reads from UserDefaults on initialization, and only internal code changes trigger the didSet handler.
Therefore, if another app or process updates your UserDefaults store, or two @DefaultsStorage properties use the same key, these changes won’t trigger reactively.
The first scenario is rare, and for the second, ensure a single variable per setting as the “source of truth.”
- Inspired by SwiftUI's
@AppStorage.