Add `Defaults.Toggle` (#69)

This commit is contained in:
Sindre Sorhus 2021-05-18 19:56:55 +07:00 committed by GitHub
parent 64351c25ed
commit 63d93f97ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 143 additions and 3 deletions

View File

@ -5,10 +5,11 @@ import Combine
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Defaults {
final class Observable<Value: Serializable>: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
private var observation: DefaultsObservation?
private let key: Defaults.Key<Value>
let objectWillChange = ObservableObjectPublisher()
var value: Value {
get { Defaults[key] }
set {
@ -44,6 +45,8 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
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>
/**
@ -61,7 +64,10 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
var body: some View {
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()
}
}
@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

View File

@ -153,6 +153,8 @@ Defaults[isUnicorn]
### 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`.
```swift
@ -165,7 +167,10 @@ struct ContentView: View {
var body: some View {
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`.
#### `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
```swift