parent
39160d389f
commit
12a65c057d
|
@ -29,6 +29,10 @@
|
||||||
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
|
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
|
||||||
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
|
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
|
||||||
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
|
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339B3B72449F10D00E7A40A /* UserDefaults.swift */; };
|
||||||
|
E38C9F27244ADA2F00A6737A /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38C9F26244ADA2F00A6737A /* SwiftUI.swift */; };
|
||||||
|
E38C9F28244ADA2F00A6737A /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38C9F26244ADA2F00A6737A /* SwiftUI.swift */; };
|
||||||
|
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38C9F26244ADA2F00A6737A /* SwiftUI.swift */; };
|
||||||
|
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38C9F26244ADA2F00A6737A /* SwiftUI.swift */; };
|
||||||
E3EB3E33216505920033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; };
|
E3EB3E33216505920033B089 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E32216505920033B089 /* util.swift */; };
|
||||||
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
||||||
E3EB3E36216507B50033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
E3EB3E36216507B50033B089 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB3E34216507AE0033B089 /* Observation.swift */; };
|
||||||
|
@ -79,6 +83,7 @@
|
||||||
E286D0C623B8D51100570D1E /* Observation+Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "Observation+Combine.swift"; sourceTree = "<group>"; usesTabs = 1; };
|
E286D0C623B8D51100570D1E /* Observation+Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "Observation+Combine.swift"; sourceTree = "<group>"; usesTabs = 1; };
|
||||||
E339B3B22449ED2000E7A40A /* Reset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Reset.swift; sourceTree = "<group>"; usesTabs = 1; };
|
E339B3B22449ED2000E7A40A /* Reset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Reset.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||||
E339B3B72449F10D00E7A40A /* UserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UserDefaults.swift; sourceTree = "<group>"; usesTabs = 1; };
|
E339B3B72449F10D00E7A40A /* UserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UserDefaults.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||||
|
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwiftUI.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||||
E3EB3E32216505920033B089 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = util.swift; sourceTree = "<group>"; usesTabs = 1; };
|
E3EB3E32216505920033B089 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = util.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||||
E3EB3E34216507AE0033B089 /* Observation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Observation.swift; sourceTree = "<group>"; usesTabs = 1; };
|
E3EB3E34216507AE0033B089 /* Observation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Observation.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
@ -215,6 +220,7 @@
|
||||||
E339B3B22449ED2000E7A40A /* Reset.swift */,
|
E339B3B22449ED2000E7A40A /* Reset.swift */,
|
||||||
E3EB3E34216507AE0033B089 /* Observation.swift */,
|
E3EB3E34216507AE0033B089 /* Observation.swift */,
|
||||||
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
|
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
|
||||||
|
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */,
|
||||||
E3EB3E32216505920033B089 /* util.swift */,
|
E3EB3E32216505920033B089 /* util.swift */,
|
||||||
);
|
);
|
||||||
path = Defaults;
|
path = Defaults;
|
||||||
|
@ -502,6 +508,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */,
|
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */,
|
||||||
|
E38C9F28244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
|
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||||
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
|
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
|
||||||
|
@ -523,6 +530,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
|
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
|
||||||
|
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
E3EB3E3A216507C40033B089 /* util.swift in Sources */,
|
E3EB3E3A216507C40033B089 /* util.swift in Sources */,
|
||||||
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
|
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
|
||||||
|
@ -536,6 +544,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
|
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
|
||||||
|
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
E3EB3E3B216507C40033B089 /* util.swift in Sources */,
|
E3EB3E3B216507C40033B089 /* util.swift in Sources */,
|
||||||
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
|
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
|
||||||
|
@ -549,6 +558,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
|
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
|
||||||
|
E38C9F27244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
E3EB3E39216507C30033B089 /* util.swift in Sources */,
|
E3EB3E39216507C30033B089 /* util.swift in Sources */,
|
||||||
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
E3EB3E36216507B50033B089 /* Observation.swift in Sources */,
|
E3EB3E36216507B50033B089 /* Observation.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
#if canImport(Combine)
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
|
extension Defaults {
|
||||||
|
final class Observable<Value: Codable>: ObservableObject {
|
||||||
|
let objectWillChange = ObservableObjectPublisher()
|
||||||
|
private var observation: DefaultsObservation?
|
||||||
|
private let key: Defaults.Key<Value>
|
||||||
|
|
||||||
|
var value: Value {
|
||||||
|
get { Defaults[key] }
|
||||||
|
set {
|
||||||
|
objectWillChange.send()
|
||||||
|
Defaults[key] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ key: Key<Value>) {
|
||||||
|
self.key = key
|
||||||
|
|
||||||
|
self.observation = Defaults.observe(key, options: [.prior]) { [weak self] change in
|
||||||
|
guard change.isPrior else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.mainSafeAsync {
|
||||||
|
self?.objectWillChange.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the key back to its default value.
|
||||||
|
func reset() {
|
||||||
|
key.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
|
@propertyWrapper
|
||||||
|
public struct Default<Value: Codable>: DynamicProperty {
|
||||||
|
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
|
||||||
|
|
||||||
|
private let key: Defaults.Key<Value>
|
||||||
|
@ObservedObject private var observable: Defaults.Observable<Value>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get/set a `Defaults` item and also have the view be updated when the value changes. This is similar to `@State`.
|
||||||
|
|
||||||
|
```
|
||||||
|
extension Defaults.Keys {
|
||||||
|
static let hasUnicorn = Key<Bool>("hasUnicorn", default: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
@Default(.hasUnicorn) var hasUnicorn
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("Has Unicorn: \(hasUnicorn)")
|
||||||
|
Toggle("Toggle Unicorn", isOn: $hasUnicorn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
public init(_ key: Defaults.Key<Value>) {
|
||||||
|
self.key = key
|
||||||
|
self.observable = Defaults.Observable(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var wrappedValue: Value {
|
||||||
|
get { observable.value }
|
||||||
|
nonmutating set {
|
||||||
|
observable.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var projectedValue: Binding<Value> { $observable.value }
|
||||||
|
|
||||||
|
/// Combine publisher that publishes values when the `Defaults` item changes.
|
||||||
|
public var publisher: Publisher { Defaults.publisher(key) }
|
||||||
|
|
||||||
|
public mutating func update() {
|
||||||
|
_observable.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Reset the key back to its default value.
|
||||||
|
|
||||||
|
```
|
||||||
|
extension Defaults.Keys {
|
||||||
|
static let opacity = Key<Double>("opacity", default: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
@Default(.opacity) var opacity
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button("Reset") {
|
||||||
|
self._opacity.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
public func reset() {
|
||||||
|
key.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -132,3 +132,17 @@ extension Optional: _DefaultsOptionalType {
|
||||||
func isOptionalType<T>(_ type: T.Type) -> Bool {
|
func isOptionalType<T>(_ type: T.Type) -> Bool {
|
||||||
type is _DefaultsOptionalType.Type
|
type is _DefaultsOptionalType.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension DispatchQueue {
|
||||||
|
/**
|
||||||
|
Performs the `execute` closure immediately if we're on the main thread or asynchronously puts it on the main thread otherwise.
|
||||||
|
*/
|
||||||
|
static func mainSafeAsync(execute work: @escaping () -> Void) {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
work()
|
||||||
|
} else {
|
||||||
|
main.async(execute: work)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
34
readme.md
34
readme.md
|
@ -13,9 +13,10 @@ It's used in production by apps like [Gifski](https://github.com/sindresorhus/Gi
|
||||||
- **Strongly typed:** You declare the type and default value upfront.
|
- **Strongly typed:** You declare the type and default value upfront.
|
||||||
- **Codable support:** You can store any [Codable](https://developer.apple.com/documentation/swift/codable) value, like an enum.
|
- **Codable support:** You can store any [Codable](https://developer.apple.com/documentation/swift/codable) value, like an enum.
|
||||||
- **NSSecureCoding support:** You can store any [NSSecureCoding](https://developer.apple.com/documentation/foundation/nssecurecoding) value.
|
- **NSSecureCoding support:** You can store any [NSSecureCoding](https://developer.apple.com/documentation/foundation/nssecurecoding) value.
|
||||||
- **Debuggable:** The data is stored as JSON-serialized values.
|
- **SwiftUI:** Property wrapper that updates the view when the `UserDefaults` value changes.
|
||||||
- **Observation:** Observe changes to keys.
|
|
||||||
- **Publishers:** Combine publishers built-in.
|
- **Publishers:** Combine publishers built-in.
|
||||||
|
- **Observation:** Observe changes to keys.
|
||||||
|
- **Debuggable:** The data is stored as JSON-serialized values.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
@ -141,6 +142,29 @@ Defaults[isUnicorn]
|
||||||
//=> true
|
//=> true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### SwiftUI support
|
||||||
|
|
||||||
|
You can use the `@Default` property wrapper to get/set a `Defaults` item and also have the view be updated when the value changes. This is similar to `@State`.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
extension Defaults.Keys {
|
||||||
|
static let hasUnicorn = Key<Bool>("hasUnicorn", default: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
@Default(.hasUnicorn) var hasUnicorn
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("Has Unicorn: \(hasUnicorn)")
|
||||||
|
Toggle("Toggle Unicorn", isOn: $hasUnicorn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it's `@Default`, not `@Defaults`.
|
||||||
|
|
||||||
|
This is only implemented for `Defaults.Key`. PR welcome for `Defaults.NSSecureCoding` if you need it.
|
||||||
|
|
||||||
### Observe changes to a key
|
### Observe changes to a key
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
@ -449,6 +473,12 @@ Break the lifetime tie created by `tieToLifetime(of:)`, if one exists.
|
||||||
|
|
||||||
The effects of any call to `tieToLifetime(of:)` are reversed. Note however that if the tied-to object has already died, then the observation is already invalid and this method has no logical effect.
|
The effects of any call to `tieToLifetime(of:)` are reversed. Note however that if the tied-to object has already died, then the observation is already invalid and this method has no logical effect.
|
||||||
|
|
||||||
|
### `@Default(_ key:)`
|
||||||
|
|
||||||
|
Get/set a `Defaults` item and also have the view be updated when the value changes.
|
||||||
|
|
||||||
|
This is only implemented for `Defaults.Key`. PR welcome for `Defaults.NSSecureCoding` if you need it.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### How can I store a dictionary of arbitrary values?
|
### How can I store a dictionary of arbitrary values?
|
||||||
|
|
Loading…
Reference in New Issue