-
-
Notifications
You must be signed in to change notification settings - Fork 60
View/environment modifier, support for both EnvironmentValues and EnvironmentKey (used for custom storage in the environment) #242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,7 +29,8 @@ struct GreetingGeneratorApp: App { | |
|
|
||
| Toggle("Selectable Greeting", active: $isGreetingSelectable) | ||
| if let latest = greetings.last { | ||
| Text(latest) | ||
| EnvironmentDisplay() | ||
| .environment(key: TestKey.self, value: latest) | ||
| .padding(.top, 5) | ||
| .textSelectionEnabled(isGreetingSelectable) | ||
|
|
||
|
|
@@ -51,3 +52,15 @@ struct GreetingGeneratorApp: App { | |
| } | ||
| } | ||
| } | ||
|
|
||
| struct EnvironmentDisplay: View { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename to |
||
| @Environment(TestKey.self) var value: String? | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a newline after this. |
||
| var body: some View { | ||
| Text(value ?? "nil") | ||
| } | ||
| } | ||
|
|
||
| struct TestKey: EnvironmentKey { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename to |
||
| typealias Value = String? | ||
| static let defaultValue: Value = nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,21 +36,26 @@ | |
| /// ``` | ||
| @propertyWrapper | ||
| public struct Environment<Value>: DynamicProperty { | ||
| var keyPath: KeyPath<EnvironmentValues, Value> | ||
| var keyPath: KeyPath<EnvironmentValues, Value>? | ||
| var environmentKey: (any EnvironmentKey.Type)? | ||
|
Comment on lines
+39
to
+40
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Introduce an enum to encode the requirement that we want one property or the other to be non-nil (not neither, and not both). This enum would just be an internal detail. enum EnvironmentLocation<Value> {
case keyPath(KeyPath<EnvironmentValues, Value>)
case environmentKey(any EnvironmentKey.Type)
}You can then replace these two properties with a single |
||
| var value: Box<Value?> | ||
|
|
||
| public func update( | ||
| with environment: EnvironmentValues, | ||
| previousValue: Self? | ||
| ) { | ||
| value.value = environment[keyPath: keyPath] | ||
| if let keyPath { | ||
| value.value = environment[keyPath: keyPath] | ||
| } else if let environmentKey { | ||
| value.value = (environment[environmentKey] as! Value) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've thought about how to avoid this force unwrap a little bit, and it seems like we can make I had to Google the syntax, so I figured I might as well include it here. To make protocol EnvironmentKey<Value> {
associatedtype Value
...
} |
||
| } | ||
| } | ||
|
|
||
| public var wrappedValue: Value { | ||
| guard let value = value.value else { | ||
| fatalError( | ||
| """ | ||
| Environment value \(keyPath) used before initialization. Don't \ | ||
| Environment value \(keyPath.debugDescription) used before initialization. Don't \ | ||
| use @Environment properties before SwiftCrossUI requests the \ | ||
| view's body. | ||
| """ | ||
|
|
@@ -63,4 +68,9 @@ public struct Environment<Value>: DynamicProperty { | |
| self.keyPath = keyPath | ||
| value = Box(value: nil) | ||
| } | ||
|
|
||
| public init<Key: EnvironmentKey>(_ type: Key.Type) where Value == Key.Value { | ||
| self.environmentKey = type | ||
| self.value = Box(value: nil) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,6 +222,14 @@ public struct EnvironmentValues { | |
| environment[keyPath: keyPath] = newValue | ||
| return environment | ||
| } | ||
|
|
||
| /// Returns a copy of the environment with the specified key set to the | ||
| /// provided new value. | ||
| public func with<T: EnvironmentKey>(key: T.Type, value: T.Value) -> Self { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Give the parameters each the underscore label to match the keypath variant (and rename |
||
| var environment = self | ||
| environment[key] = value | ||
| return environment | ||
| } | ||
| } | ||
|
|
||
| /// A key that can be used to extend the environment with new properties. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,3 +37,21 @@ package struct EnvironmentModifier<Child: View>: View { | |
| ) | ||
| } | ||
| } | ||
|
|
||
| extension View { | ||
| /// Modifies the environment of the View its applied to. | ||
| public func environment<T: EnvironmentKey>(key: T.Type, value: T.Value) -> some View { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the parameter labels to match the following method, and rename |
||
| EnvironmentModifier(self) { environment in | ||
| environment.with(key: key, value: value) | ||
| } | ||
| } | ||
|
|
||
| /// Modifies the environment of the View its applied to | ||
| public func environment<T>(_ keyPath: WritableKeyPath<EnvironmentValues, T>, _ newValue: T) | ||
| -> some View | ||
| { | ||
| EnvironmentModifier(self) { environment in | ||
| environment.with(keyPath, newValue) | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a doc comment along the lines of
/// This intermediate view exists to show the usage of custom environment keys. In reality it is not necessary.so that future readers don't get too confused.