-
-
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?
Conversation
…or set a value for an EnvironmentKey
stackotter
left a comment
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.
Thanks for these changes! They will make working with custom environment values much easier.
While reviewing the PR, I googled stuff about SwiftUI's environment modifiers, and I found that SwiftUI works around the distinction between custom keys and builtin environment properties (keypaths) by instructing users to extend EnvironmentValues with their own custom properties that utilise the environment key. Your implementation doesn't stop users from taking that approach, but I'm wondering whether we should try to push users towards the SwiftUI approach, or instead let them choose.
I was leaning towards letting users choose because it makes defining custom environment values very simple (just define the key). However, I realised that having these two different types of environment properties makes it tricky for libraries to provide methods that are generic over environment properties, because they would need to vend two separate (but very similar) methods for each abstraction.
For that reason, I think that it's probably best to not have the environment key based methods? But I'm open to discussion on that.
| } | ||
|
|
||
| struct EnvironmentDisplay: View { | ||
| @Environment(TestKey.self) var value: String? |
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 newline after this.
| } | ||
| } | ||
|
|
||
| struct EnvironmentDisplay: View { |
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.
| } | ||
| } | ||
|
|
||
| struct TestKey: EnvironmentKey { |
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.
Rename to LatestGreetingKey to make its usage more self-evident.
| } | ||
| } | ||
|
|
||
| struct EnvironmentDisplay: View { |
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.
Rename to LatestGreetingDisplay for clarity.
| var keyPath: KeyPath<EnvironmentValues, Value>? | ||
| var environmentKey: (any EnvironmentKey.Type)? |
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.
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 location: EnvironmentLocation<Value> property, and then replace the existing if lets with switch statements.
| if let keyPath { | ||
| value.value = environment[keyPath: keyPath] | ||
| } else if let environmentKey { | ||
| value.value = (environment[environmentKey] as! Value) |
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.
I've thought about how to avoid this force unwrap a little bit, and it seems like we can make Value a primary associated type of EnvironmentKey, and then update the environmentKey property to have type any EnvironmentKey<Value>.Type. This should make things a bit safer, and appears to be a backwards compatible change (because users of EnvironmentKey aren't forced to use the primary associated type syntax)?
I had to Google the syntax, so I figured I might as well include it here. To make Value a primary associatetype do the following;
protocol EnvironmentKey<Value> {
associatedtype Value
...
}|
|
||
| /// 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 { |
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.
Give the parameters each the underscore label to match the keypath variant (and rename value to newValue)
|
|
||
| extension View { | ||
| /// Modifies the environment of the View its applied to. | ||
| public func environment<T: EnvironmentKey>(key: T.Type, value: T.Value) -> some View { |
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.
Remove the parameter labels to match the following method, and rename value to newValue
This PR adds:
For demonstration and testing purposes I changed GreetingGeneratorExample’s latest greeting display to get its value over an environment key.
The @Environment property wrapper was changed to additionally support EnvironmentKeys.
I did some limited testing which worked like expected. The modifiers just use the EnvironmentModifier View just like we do with most other modifiers.