Add `Defaults.Toggle` (#69)
This commit is contained in:
parent
64351c25ed
commit
63d93f97ad
|
@ -5,10 +5,11 @@ import Combine
|
||||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
extension Defaults {
|
extension Defaults {
|
||||||
final class Observable<Value: Serializable>: ObservableObject {
|
final class Observable<Value: Serializable>: ObservableObject {
|
||||||
let objectWillChange = ObservableObjectPublisher()
|
|
||||||
private var observation: DefaultsObservation?
|
private var observation: DefaultsObservation?
|
||||||
private let key: Defaults.Key<Value>
|
private let key: Defaults.Key<Value>
|
||||||
|
|
||||||
|
let objectWillChange = ObservableObjectPublisher()
|
||||||
|
|
||||||
var value: Value {
|
var value: Value {
|
||||||
get { Defaults[key] }
|
get { Defaults[key] }
|
||||||
set {
|
set {
|
||||||
|
@ -44,6 +45,8 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
|
||||||
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
|
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
|
||||||
|
|
||||||
private let key: Defaults.Key<Value>
|
private let key: Defaults.Key<Value>
|
||||||
|
|
||||||
|
// Intentionally using `@ObservedObjected` over `@StateObject` so that the key can be dynamicaly changed.
|
||||||
@ObservedObject private var observable: Defaults.Observable<Value>
|
@ObservedObject private var observable: Defaults.Observable<Value>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,7 +64,10 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text("Has Unicorn: \(hasUnicorn)")
|
Text("Has Unicorn: \(hasUnicorn)")
|
||||||
Toggle("Toggle Unicorn", isOn: $hasUnicorn)
|
Toggle("Toggle", isOn: $hasUnicorn)
|
||||||
|
Button("Reset") {
|
||||||
|
_hasUnicorn.reset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -110,4 +116,101 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
|
||||||
key.reset()
|
key.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(macOS 11, iOS 14, tvOS 14, watchOS 7, *)
|
||||||
|
extension Defaults {
|
||||||
|
/**
|
||||||
|
Creates a SwiftUI `Toggle` view that is connected to a `Defaults` key with a `Bool` value.
|
||||||
|
|
||||||
|
The toggle works exactly like the SwiftUI `Toggle`.
|
||||||
|
|
||||||
|
```
|
||||||
|
extension Defaults.Keys {
|
||||||
|
static let showAllDayEvents = Key<Bool>("showAllDayEvents", default: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShowAllDayEventsSetting: View {
|
||||||
|
var body: some View {
|
||||||
|
Defaults.Toggle("Show All-Day Events", key: .showAllDayEvents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also listen to changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
struct ShowAllDayEventsSetting: View {
|
||||||
|
var body: some View {
|
||||||
|
Defaults.Toggle("Show All-Day Events", key: .showAllDayEvents)
|
||||||
|
// Note that this has to be directly attached to `Defaults.Toggle`. It's not `View#onChange()`.
|
||||||
|
.onChange {
|
||||||
|
print("Value", $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
public struct Toggle<Label, Key>: View where Label: View, Key: Defaults.Key<Bool> {
|
||||||
|
@ViewStorage private var onChange: ((Bool) -> Void)?
|
||||||
|
|
||||||
|
private let label: () -> Label
|
||||||
|
|
||||||
|
// Intentionally using `@ObservedObjected` over `@StateObject` so that the key can be dynamicaly changed.
|
||||||
|
@ObservedObject private var observable: Defaults.Observable<Bool>
|
||||||
|
|
||||||
|
public init(key: Key, @ViewBuilder label: @escaping () -> Label) {
|
||||||
|
self.label = label
|
||||||
|
self.observable = Defaults.Observable(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
SwiftUI.Toggle(isOn: $observable.value, label: label)
|
||||||
|
.onChange(of: observable.value) {
|
||||||
|
onChange?($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 11, iOS 14, tvOS 14, watchOS 7, *)
|
||||||
|
extension Defaults.Toggle where Label == Text {
|
||||||
|
public init<S>(_ title: S, key: Defaults.Key<Bool>) where S: StringProtocol {
|
||||||
|
self.label = { Text(title) }
|
||||||
|
self.observable = Defaults.Observable(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 11, iOS 14, tvOS 14, watchOS 7, *)
|
||||||
|
extension Defaults.Toggle {
|
||||||
|
/// Do something when the value changes to a different value.
|
||||||
|
public func onChange(_ action: @escaping (Bool) -> Void) -> Self {
|
||||||
|
onChange = action
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
|
@propertyWrapper
|
||||||
|
private struct ViewStorage<Value>: DynamicProperty {
|
||||||
|
private final class ValueBox {
|
||||||
|
var value: Value
|
||||||
|
|
||||||
|
init(_ value: Value) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var valueBox: ValueBox
|
||||||
|
|
||||||
|
var wrappedValue: Value {
|
||||||
|
get { valueBox.value }
|
||||||
|
nonmutating set {
|
||||||
|
valueBox.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(wrappedValue value: @autoclosure @escaping () -> Value) {
|
||||||
|
self._valueBox = .init(wrappedValue: ValueBox(value()))
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
39
readme.md
39
readme.md
|
@ -153,6 +153,8 @@ Defaults[isUnicorn]
|
||||||
|
|
||||||
### SwiftUI support
|
### SwiftUI support
|
||||||
|
|
||||||
|
#### `@Default`
|
||||||
|
|
||||||
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`.
|
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
|
```swift
|
||||||
|
@ -165,7 +167,10 @@ struct ContentView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text("Has Unicorn: \(hasUnicorn)")
|
Text("Has Unicorn: \(hasUnicorn)")
|
||||||
Toggle("Toggle Unicorn", isOn: $hasUnicorn)
|
Toggle("Toggle", isOn: $hasUnicorn)
|
||||||
|
Button("Reset") {
|
||||||
|
_hasUnicorn.reset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -174,6 +179,38 @@ Note that it's `@Default`, not `@Defaults`.
|
||||||
|
|
||||||
You cannot use `@Default` in an `ObservableObject`. It's meant to be used in a `View`.
|
You cannot use `@Default` in an `ObservableObject`. It's meant to be used in a `View`.
|
||||||
|
|
||||||
|
#### `Toggle`
|
||||||
|
|
||||||
|
There's also a `SwiftUI.Toggle` wrapper that makes it easier to create a toggle based on a `Defaults` key with a `Bool` value.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
extension Defaults.Keys {
|
||||||
|
static let showAllDayEvents = Key<Bool>("showAllDayEvents", default: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShowAllDayEventsSetting: View {
|
||||||
|
var body: some View {
|
||||||
|
Defaults.Toggle("Show All-Day Events", key: .showAllDayEvents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also listen to changes:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct ShowAllDayEventsSetting: View {
|
||||||
|
var body: some View {
|
||||||
|
Defaults.Toggle("Show All-Day Events", key: .showAllDayEvents)
|
||||||
|
// Note that this has to be directly attached to `Defaults.Toggle`. It's not `View#onChange()`.
|
||||||
|
.onChange {
|
||||||
|
print("Value", $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Requires at least macOS 11, iOS 14, tvOS 14, watchOS 7.*
|
||||||
|
|
||||||
### Observe changes to a key
|
### Observe changes to a key
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
|
Loading…
Reference in New Issue