From 3ad11e6867c27225b73f0fb6d2796465a066a4b4 Mon Sep 17 00:00:00 2001 From: Mia Koring Date: Thu, 30 Oct 2025 00:52:20 +0100 Subject: [PATCH 1/5] added .environment modifiers to modify a predefined EnvironmentValue or set a value for an EnvironmentKey --- .../GreetingGeneratorApp.swift | 15 ++++++++++++++- .../SwiftCrossUI/Environment/Environment.swift | 14 ++++++++++++-- .../Environment/EnvironmentValues.swift | 8 ++++++++ .../Views/Modifiers/EnvironmentModifier.swift | 18 ++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) 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..641518a6ae 100644 --- a/Sources/SwiftCrossUI/Environment/Environment.swift +++ b/Sources/SwiftCrossUI/Environment/Environment.swift @@ -36,14 +36,19 @@ /// ``` @propertyWrapper public struct Environment: DynamicProperty { - var keyPath: KeyPath + var keyPath: KeyPath? + var environmentKey: 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 { @@ -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 ffb03d5d94..8922ef82e0 100644 --- a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift +++ b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift @@ -221,6 +221,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) + } + } +} From ae7430ecc3785cbe82928080aed8a9740b82c8eb Mon Sep 17 00:00:00 2001 From: Mia Koring Date: Mon, 3 Nov 2025 17:06:56 +0100 Subject: [PATCH 2/5] should fix CI issues caused by different swift version than I apparently use locally --- Sources/SwiftCrossUI/Environment/Environment.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftCrossUI/Environment/Environment.swift b/Sources/SwiftCrossUI/Environment/Environment.swift index 641518a6ae..9da46992aa 100644 --- a/Sources/SwiftCrossUI/Environment/Environment.swift +++ b/Sources/SwiftCrossUI/Environment/Environment.swift @@ -37,7 +37,7 @@ @propertyWrapper public struct Environment: DynamicProperty { var keyPath: KeyPath? - var environmentKey: EnvironmentKey.Type? + var environmentKey: (any EnvironmentKey.Type)? var value: Box public func update( @@ -47,7 +47,7 @@ public struct Environment: DynamicProperty { if let keyPath { value.value = environment[keyPath: keyPath] } else if let environmentKey { - value.value = environment[environmentKey] as! Value + value.value = (environment[environmentKey] as! Value) } } @@ -55,7 +55,7 @@ public struct Environment: DynamicProperty { 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. """ From 132bd77e43d49d50126cdbc3a0ac0b55f2d0055d Mon Sep 17 00:00:00 2001 From: Mia Koring Date: Sun, 14 Dec 2025 00:42:29 +0100 Subject: [PATCH 3/5] Requested changes part 1 (transition to keypath missing) --- .../GreetingGeneratorApp.swift | 12 ++++--- .../Environment/Environment.swift | 34 ++++++++++++++----- .../Environment/EnvironmentValues.swift | 6 ++-- .../Views/Modifiers/EnvironmentModifier.swift | 4 +-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift index 6438069423..7734bd196f 100644 --- a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift +++ b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift @@ -29,8 +29,8 @@ struct GreetingGeneratorApp: App { Toggle("Selectable Greeting", active: $isGreetingSelectable) if let latest = greetings.last { - EnvironmentDisplay() - .environment(key: TestKey.self, value: latest) + LatestGreetingDisplay() + .environment(LatestGreetingKey.self, latest) .padding(.top, 5) .textSelectionEnabled(isGreetingSelectable) @@ -53,14 +53,16 @@ struct GreetingGeneratorApp: App { } } -struct EnvironmentDisplay: View { - @Environment(TestKey.self) var value: String? +/// This intermediate view exists to show the usage of custom environment keys. In reality it is not necessary. +struct LatestGreetingDisplay: View { + @Environment(LatestGreetingKey.self) var value: String? + var body: some View { Text(value ?? "nil") } } -struct TestKey: EnvironmentKey { +struct LatestGreetingKey: EnvironmentKey { typealias Value = String? static let defaultValue: Value = nil } diff --git a/Sources/SwiftCrossUI/Environment/Environment.swift b/Sources/SwiftCrossUI/Environment/Environment.swift index 9da46992aa..87cb9b7368 100644 --- a/Sources/SwiftCrossUI/Environment/Environment.swift +++ b/Sources/SwiftCrossUI/Environment/Environment.swift @@ -36,18 +36,18 @@ /// ``` @propertyWrapper public struct Environment: DynamicProperty { - var keyPath: KeyPath? - var environmentKey: (any EnvironmentKey.Type)? + var location: EnvironmentLocation var value: Box public func update( with environment: EnvironmentValues, previousValue: Self? ) { - if let keyPath { - value.value = environment[keyPath: keyPath] - } else if let environmentKey { - value.value = (environment[environmentKey] as! Value) + switch location { + case .keyPath(let keyPath): + value.value = environment[keyPath: keyPath] + case .environmentKey(let environmentKey): + value.value = (environment[environmentKey]) } } @@ -55,7 +55,7 @@ public struct Environment: DynamicProperty { guard let value = value.value else { fatalError( """ - Environment value \(keyPath.debugDescription) used before initialization. Don't \ + Environment value at \(location.debugDescription) used before initialization. Don't \ use @Environment properties before SwiftCrossUI requests the \ view's body. """ @@ -65,12 +65,28 @@ public struct Environment: DynamicProperty { } public init(_ keyPath: KeyPath) { - self.keyPath = keyPath + self.location = .keyPath(keyPath) value = Box(value: nil) } public init(_ type: Key.Type) where Value == Key.Value { - self.environmentKey = type + self.location = .environmentKey(type) self.value = Box(value: nil) } } + +enum EnvironmentLocation { + case keyPath(KeyPath) + case environmentKey(any EnvironmentKey.Type) +} + +extension EnvironmentLocation: CustomDebugStringConvertible { + var debugDescription: String { + switch self { + case .keyPath(let keyPath): + "EnvironmentLocation.keyPath(\(keyPath))" + case .environmentKey(let environmentKey): + "EnvironmentLocation.environmentKey(\(environmentKey))" + } + } +} diff --git a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift index 46fd6427b9..613a893ab4 100644 --- a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift +++ b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift @@ -225,15 +225,15 @@ public struct EnvironmentValues { /// 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 { + public func with(_ key: T.Type, _ newValue: T.Value) -> Self { var environment = self - environment[key] = value + environment[key] = newValue return environment } } /// A key that can be used to extend the environment with new properties. -public protocol EnvironmentKey { +public protocol EnvironmentKey { /// The type of value the key can hold. associatedtype Value /// The default value for the key. diff --git a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift index c3c05a6fcc..1e334fb780 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift @@ -40,9 +40,9 @@ 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 { + public func environment(_ key: T.Type, _ newValue: T.Value) -> some View { EnvironmentModifier(self) { environment in - environment.with(key: key, value: value) + environment.with(key, newValue) } } From 141d630065e1d83d823418e32dac7367c40751dd Mon Sep 17 00:00:00 2001 From: Mia Koring Date: Sun, 14 Dec 2025 00:52:12 +0100 Subject: [PATCH 4/5] formatter hit --- .../Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift index 7734bd196f..b857b9dbf7 100644 --- a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift +++ b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift @@ -56,7 +56,7 @@ struct GreetingGeneratorApp: App { /// This intermediate view exists to show the usage of custom environment keys. In reality it is not necessary. struct LatestGreetingDisplay: View { @Environment(LatestGreetingKey.self) var value: String? - + var body: some View { Text(value ?? "nil") } From 9bb26960bd9af4d5282bd2303317faa7516b8dc2 Mon Sep 17 00:00:00 2001 From: Mia Koring Date: Sun, 14 Dec 2025 06:44:18 +0100 Subject: [PATCH 5/5] Removed public EnvironmentKey apis except self[] --- .../GreetingGeneratorApp.swift | 11 ++++-- .../Environment/Environment.swift | 34 +++---------------- .../Environment/EnvironmentValues.swift | 8 ----- .../Views/Modifiers/EnvironmentModifier.swift | 7 ---- 4 files changed, 13 insertions(+), 47 deletions(-) diff --git a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift index b857b9dbf7..4434769400 100644 --- a/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift +++ b/Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift @@ -30,7 +30,7 @@ struct GreetingGeneratorApp: App { Toggle("Selectable Greeting", active: $isGreetingSelectable) if let latest = greetings.last { LatestGreetingDisplay() - .environment(LatestGreetingKey.self, latest) + .environment(\.latestGreeting, latest) .padding(.top, 5) .textSelectionEnabled(isGreetingSelectable) @@ -55,7 +55,7 @@ struct GreetingGeneratorApp: App { /// This intermediate view exists to show the usage of custom environment keys. In reality it is not necessary. struct LatestGreetingDisplay: View { - @Environment(LatestGreetingKey.self) var value: String? + @Environment(\.latestGreeting) var value: String? var body: some View { Text(value ?? "nil") @@ -66,3 +66,10 @@ struct LatestGreetingKey: EnvironmentKey { typealias Value = String? static let defaultValue: Value = nil } + +extension EnvironmentValues { + var latestGreeting: String? { + get { self[LatestGreetingKey.self] } + set { self[LatestGreetingKey.self] = newValue } + } +} diff --git a/Sources/SwiftCrossUI/Environment/Environment.swift b/Sources/SwiftCrossUI/Environment/Environment.swift index 87cb9b7368..a7a42669d0 100644 --- a/Sources/SwiftCrossUI/Environment/Environment.swift +++ b/Sources/SwiftCrossUI/Environment/Environment.swift @@ -36,26 +36,21 @@ /// ``` @propertyWrapper public struct Environment: DynamicProperty { - var location: EnvironmentLocation + var keyPath: KeyPath var value: Box public func update( with environment: EnvironmentValues, previousValue: Self? ) { - switch location { - case .keyPath(let keyPath): - value.value = environment[keyPath: keyPath] - case .environmentKey(let environmentKey): - value.value = (environment[environmentKey]) - } + value.value = environment[keyPath: keyPath] } public var wrappedValue: Value { guard let value = value.value else { fatalError( """ - Environment value at \(location.debugDescription) used before initialization. Don't \ + Environment value at \(keyPath) used before initialization. Don't \ use @Environment properties before SwiftCrossUI requests the \ view's body. """ @@ -65,28 +60,7 @@ public struct Environment: DynamicProperty { } public init(_ keyPath: KeyPath) { - self.location = .keyPath(keyPath) + self.keyPath = keyPath value = Box(value: nil) } - - public init(_ type: Key.Type) where Value == Key.Value { - self.location = .environmentKey(type) - self.value = Box(value: nil) - } -} - -enum EnvironmentLocation { - case keyPath(KeyPath) - case environmentKey(any EnvironmentKey.Type) -} - -extension EnvironmentLocation: CustomDebugStringConvertible { - var debugDescription: String { - switch self { - case .keyPath(let keyPath): - "EnvironmentLocation.keyPath(\(keyPath))" - case .environmentKey(let environmentKey): - "EnvironmentLocation.environmentKey(\(environmentKey))" - } - } } diff --git a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift index 613a893ab4..082ede2354 100644 --- a/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift +++ b/Sources/SwiftCrossUI/Environment/EnvironmentValues.swift @@ -222,14 +222,6 @@ 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, _ newValue: T.Value) -> Self { - var environment = self - environment[key] = newValue - 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 1e334fb780..2470da3fb0 100644 --- a/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift +++ b/Sources/SwiftCrossUI/Views/Modifiers/EnvironmentModifier.swift @@ -39,13 +39,6 @@ package struct EnvironmentModifier: View { } extension View { - /// Modifies the environment of the View its applied to. - public func environment(_ key: T.Type, _ newValue: T.Value) -> some View { - EnvironmentModifier(self) { environment in - environment.with(key, newValue) - } - } - /// Modifies the environment of the View its applied to public func environment(_ keyPath: WritableKeyPath, _ newValue: T) -> some View