https://github.com/lessica/observableuserdefaults
An easy-to-use UserDefaults extension.
https://github.com/lessica/observableuserdefaults
cocoa kvo macos swift userdefaults
Last synced: 7 months ago
JSON representation
An easy-to-use UserDefaults extension.
- Host: GitHub
- URL: https://github.com/lessica/observableuserdefaults
- Owner: Lessica
- Created: 2021-04-14T07:04:04.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2021-04-18T03:29:22.000Z (almost 5 years ago)
- Last Synced: 2025-07-07T12:42:27.244Z (8 months ago)
- Topics: cocoa, kvo, macos, swift, userdefaults
- Language: Swift
- Homepage:
- Size: 7.81 KB
- Stars: 8
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ObservableUserDefaults
Original: https://stackoverflow.com/a/54889233/5227717
These extensions make `UserDefaults` observable and easy to use.
## What's wrong?
See documentation of `UserDefaults.didChangeNotification`
> ### Summary
>
> Posted when user defaults are changed within the current process.
>
>
> ### Declaration
>
> ```swift
> public class let didChangeNotification: NSNotification.Name
> ```
>
>
> ### Discussion
>
> This notification is posted on the thread that changes the user defaults. The notification object is the UserDefaults object. The notification **doesn't contain a userInfo dictionary**.
> This notification **isn't posted when changes are made outside the current process**, or when ubiquitous defaults change. You can use key-value observing to register observers for specific keys of interest in order to be notified of all updates, regardless of whether changes are made within or outside the current process.
>
## Example & Usage
### Define Keys
```swift
// IMPORTANT: DON'T use DOT `.` in key.
// DOT `.` used to define `KeyPath` and this is what we don't need here.
extension UserDefaults.Key {
static let AppleMomentumScrollSupported : UserDefaults.Key = "AppleMomentumScrollSupported" // Bool
static let enableNetworkDiscovery : UserDefaults.Key = "defaults:enableNetworkDiscovery" // Bool
static let usesDetailedToolTips : UserDefaults.Key = "defaults:usesDetailedToolTips" // Bool
static let screenshotSavingPath : UserDefaults.Key = "defaults:screenshotSavingPath" // String
}
```
### Register Initial Values
```swift
// From Source
var initialValues: [UserDefaults.Key: Any?] = [
.enableNetworkDiscovery : true,
.usesDetailedToolTips : false,
.screenshotSavingPath : FileManager.default
.urls(for: .picturesDirectory, in: .userDomainMask)
.first?.appendingPathComponent("JSTColorPicker").path,
.pixelMatchAAColor : NSColor.systemYellow,
.pixelMatchDiffColor : NSColor.systemRed,
]
// From Property List
(try?
PropertyListSerialization.propertyList(
from: Data(contentsOf: Bundle.main.url(forResource: "InitialValues", withExtension: "plist")!),
options: [],
format: nil
)
as? [String : Any?])?.forEach({ initialValues[UserDefaults.Key(rawValue: $0.key)] = $0.value })
UserDefaults.standard.register(defaults: initialValues)
```
### Access Direct Values
```swift
let discoveryEnabled: Bool = UserDefaults.standard[.enableNetworkDiscovery]
// Access Optional Values
let savingPath: String? = UserDefaults.standard[.screenshotSavingPath]
guard let savingPath: String = UserDefaults.standard[.screenshotSavingPath] else { return }
```
### Modify Direct Values
```swift
@IBAction func discoveryMenuItemTapped(_ sender: NSMenuItem) {
let enabled = sender.state == .on
sender.state = !enabled ? .on : .off
UserDefaults.standard[.enableNetworkDiscovery] = !enabled
}
```
### Observe Changes of Single Value
```swift
// You must keep a reference to this object.
let observable = UserDefaults.standard.observe(key: .AppleMomentumScrollSupported) { (defaults, defaultKey, defaultValue: Bool) in
let momentumScrollSupported = defaultValue
// do something
}
```
### Observe Changes of Multiple Values
```swift
class MyClass {
private var observables : [Observable]?
private var usesDetailedToolTips : Bool = false
private func setupHandlers() {
observables = UserDefaults.standard.observe(
keys: [.AppleMomentumScrollSupported, .usesDetailedToolTips],
callback: { [weak self] in self?.applyDefaults($0, $1, $2) /* You must use weak self here! */ }
)
}
private func applyDefaults(_ defaults: UserDefaults, _ defaultKey: UserDefaults.Key, _ defaultValue: Any) {
if defaultKey == .usesDetailedToolTips, let toValue = defaultValue as? Bool {
if usesDetailedToolTips != toValue {
usesDetailedToolTips = toValue
// do something
}
}
}
}
```