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
UserDefaults
values. - Supports
Bool
,Int
,Double
,String
,Date
,Data
,URL
, and enums withString
orInt
raw 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.0.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
.