A lightweight property wrapper for UserDefaults done right
Read the post: A better approach to writing a UserDefaults Property Wrapper
Foil, as in let me quickly and easily wrap and store this leftover food in some foil so I can eat it later. 🌯 😉
Foil:
noun
North America
A very thin, pliable, easily torn sheet of aluminum used for cooking, packaging, cosmetics, and insulation.
You can use @WrappedDefault
for non-optional values and @WrappedDefaultOptional
for optional ones.
You may wish to store all your user defaults in one place, however, that is not necessary. Any property on any type can use this wrapper.
final class AppSettings {
static let shared = AppSettings()
@WrappedDefault(keyName: "flagEnabled", defaultValue: true)
var flagEnabled: Bool
@WrappedDefault(keyName: "totalCount", defaultValue: 0)
var totalCount: Int
@WrappedDefaultOptional(keyName: "timestamp")
var timestamp: Date?
}
// Usage
func userDidToggleSetting(_ sender: UISwitch) {
AppSettings.shared.flagEnabled = sender.isOn
}
There is also an included example app project.
If you prefer using an enum
for the keys, writing an extension specific to your app is easy. However, this is not required. In fact, unless you have a specific reason to reference the keys, this is completely unneccessary.
enum AppSettingsKey: String, CaseIterable {
case flagEnabled
case totalCount
case timestamp
}
extension WrappedDefault {
init(key: AppSettingsKey, defaultValue: T) {
self.init(keyName: key.rawValue, defaultValue: defaultValue)
}
}
extension WrappedDefaultOptional {
init(key: AppSettingsKey) {
self.init(keyName: key.rawValue)
}
}
There are many ways to observe property changes. The most common are by using Key-Value Observing or a Combine Publisher. Both require the object with the property to inherit from NSObject
and the property must be declared as @objc dynamic
.
final class AppSettings: NSObject {
static let shared = AppSettings()
@WrappedDefaultOptional(keyName: "userId")
@objc dynamic var userId: String?
}
let observer = AppSettings.shared.observe(\.userId, options: [.new]) { settings, change in
print(change)
}
AppSettings.shared
.publisher(for: \.userId, options: [.new])
.sink {
print($0)
}
.store(in: &cancellable)
The following types are supported by default for use with @WrappedDefault
.
Adding support for custom types is possible by conforming to UserDefaultsSerializable
. However, this is highly discouraged. UserDefaults
is not intended for storing complex data structures and object graphs. You should probably be using a proper database (or serializing to disk via Codable
) instead.
Bool
Int
UInt
Float
Double
String
URL
Date
Data
Array
Set
Dictionary
RawRepresentable
types
- NSUserDefaults in Practice, the excellent guide by David Smith
- UserDefaults documentation
- Preferences and Settings Programming Guide
- Property List Programming Guide
- iOS 9.0+
- tvOS 9.0+
- watchOS 5.0+
- macOS 10.13+
- Swift 5.3+
- Xcode 12.0+
- SwiftLint
pod 'Foil', '~> 1.0.0'
dependencies: [
.package(url: "https://github.com/jessesquires/Foil.git", from: "1.0.0")
]
Alternatively, you can add the package directly via Xcode.
You can read the documentation here. Generated with jazzy. Hosted by GitHub Pages.
Interested in making contributions to this project? Please review the guides below.
Also, consider sponsoring this project or buying my apps! ✌️
Created and maintained by Jesse Squires.
Released under the MIT License. See LICENSE
for details.
Copyright © 2021-present Jesse Squires.