diff --git a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift index fa4b984eff..6438069423 100644 --- a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift +++ b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift @@ -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 { + @Environment(TestKey.self) var value: String? + var body: some View { + Text(value ?? "nil") + } +} + +struct TestKey: EnvironmentKey { + typealias Value = String? + static let defaultValue: Value = nil +} diff --git a/Sources/SwiftCrossUI/Environment/Environment.swift b/Sources/SwiftCrossUI/Environment/Environment.swift index 482dfb25f0..9da46992aa 100644 --- a/Sources/SwiftCrossUI/Environment/Environment.swift +++ b/Sources/SwiftCrossUI/Environment/Environment.swift @@ -36,21 +36,26 @@ /// ``` @propertyWrapper public struct Environment: DynamicProperty { - var keyPath: KeyPath + var keyPath: KeyPath? + var environmentKey: (any EnvironmentKey.Type)? var value: Box 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) + } } 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: DynamicProperty { self.keyPath = keyPath value = Box(value: nil) } + + public init(_ type: Key.Type) where Value == Key.Value { + self.environmentKey = type + self.value = Box(value: nil) + } } diff --git a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift index 99ab536d93..46fd6427b9 100644 --- a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift +++ b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift @@ -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(key: T.Type, value: T.Value) -> Self { + var environment = self + environment[key] = value + return environment + } } /// A key that can be used to extend the environment with new properties. diff --git a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift index 801354c213..c3c05a6fcc 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift @@ -37,3 +37,21 @@ package struct EnvironmentModifier: View { ) } } + +extension View { + /// Modifies the environment of the View its applied to. + public func environment(key: T.Type, value: T.Value) -> some View { + EnvironmentModifier(self) { environment in + environment.with(key: key, value: value) + } + } + + /// Modifies the environment of the View its applied to + public func environment(_ keyPath: WritableKeyPath, _ newValue: T) + -> some View + { + EnvironmentModifier(self) { environment in + environment.with(keyPath, newValue) + } + } +}