I'm trying to require thing
being supplied instead of providing it as a default with defaultValue = Thing()
. If I make the environment optional with Thing?, then I have to unwrap it in each View. Without such an approach my initializer always appears to be called twice when I need to pass in a specific instance.
I'm trying with the code below, but it throws the fatal "Thing not provided" as soon as it runs. I'm providing the environment in thingApp, though, so I'm confused why this is happening. It happens even when @Environment(\.thing) var thing
isn't used.
import SwiftUI
@main
struct thingApp: App {
var thing = Thing()
var body: some Scene {
WindowGroup {
ContentView().environment(\.thing, thing)
}
}
}
struct ContentView: View {
@Environment(\.thing) var thing
var body: some View {
Text("count: \(self.thing.count)")
}
}
@MainActor
@Observable class Thing {
var count: Int = 3
func inc() {
self.count = self.count + 1
}
}
private struct ThingKey: EnvironmentKey {
static var defaultValue: Thing {
fatalError("Thing not provided")
}
}
extension EnvironmentValues {
var thing: Thing {
get { self[ThingKey.self] }
set { self[ThingKey.self] = newValue }
}
}
As ITGuy's answer says, you should use the other initialiser of Environment
if the environment value is @Observable
. However, this wouldn't work if you want to have multiple keys of the same type, or if your environment value is a value type.
Another way is to create your own version of Environment
that fatalError
s when the environment is not set.
@propertyWrapper
struct ForcedEnvironment<Value>: DynamicProperty {
@Environment private var env: Value?
init(_ keyPath: KeyPath<EnvironmentValues, Value?>) {
_env = Environment(keyPath)
}
var wrappedValue: Value {
if let env {
return env
} else {
fatalError("\(Value.self) not provided")
}
}
}
extension EnvironmentValues {
@Entry var thing: Thing? = nil
}
This takes a key path to an optional environment value. nil
is treated as "not set", so remember to change the EnvironmentValues
extension accordingly.
Note that this only fatalError
s when the wrappedValue
getter is called.
Usage:
struct ContentView: View {
@ForcedEnvironment(\.thing) var thing
var body: some View {
Text("count: \(self.thing.count)")
}
}