Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -51,3 +52,15 @@ struct GreetingGeneratorApp: App {
}
}
}

struct EnvironmentDisplay: View {
Copy link
Owner

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.

Copy link
Owner

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.

@Environment(TestKey.self) var value: String?
Copy link
Owner

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.

var body: some View {
Text(value ?? "nil")
}
}

struct TestKey: EnvironmentKey {
Copy link
Owner

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.

typealias Value = String?
static let defaultValue: Value = nil
}
16 changes: 13 additions & 3 deletions Sources/SwiftCrossUI/Environment/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Owner

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.

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)
Copy link
Owner

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
    ...
}

}
}

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.
"""
Expand All @@ -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)
}
}
8 changes: 8 additions & 0 deletions Sources/SwiftCrossUI/Environment/EnvironmentValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Owner

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)

var environment = self
environment[key] = value
return environment
}
}

/// A key that can be used to extend the environment with new properties.
Expand Down
18 changes: 18 additions & 0 deletions Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Owner

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

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)
}
}
}
Loading