10

I'm trying to use an @EnvironmentObject to pass data to my SwiftUI view:

struct MyView: View {
    @EnvironmentObject var myInt: Int // ❌ Property type 'Int' does not match that of the 'wrappedValue' property of its wrapper type 'EnvironmentObject'

    var body: some View {
        EmptyView()
    }
}
func constructView() {
    let myInt = 1
    MyView()
        .environmentObject(myInt)
}

The line with @EnvironmentObject had a compiler error (listed above).

How do I use @EnvironmentObject with an Int?


Update: One thought was that @EnvironmentObject can only be used with classes that conform to ObservableObject, so I tried switching to @Environment which now that part compiled, but produced a different error:

struct MyView: View {
    @Environment var myInt: Int

    var body: some View {
        EmptyView()
    }
}
func constructView() {
    let myInt = 1
    MyView() // ❌ Missing argument for parameter 'myInt' in call
        .environment(\.myInt, myInt)
}

Now when I attempt to construct it, it complains that myInt isn't set.

Senseful
  • 86,719
  • 67
  • 308
  • 465

2 Answers2

10

@EnvironmentObject can only be used with a class. Int is actually a struct.

If you did want to try to share that Int between Views, you could wrap the value in a class:

class SharedInt: ObservableObject {
    @Published var myInt = 1
}

Also, it should look more like this inside SceneDelegate.swift:

let contentView = ContentView().environmentObject(SharedInt())

if let windowScene = /* ... */

Or in SwiftUI lifecycle's ...App.swift:

WindowGroup {
    ContentView()
        .environmentObject(SharedInt())
}
George
  • 25,988
  • 10
  • 79
  • 133
  • Thanks! I thought I could save time by creating a `class EnvironmentObjectWrapper: ObservableObject` that wraps the value, but the SwiftUI environment seems to disambiguate on the type of object, not on any sort of key name, so such a wrapper would easily conflict with other values. It appears you're better off creating a different Environment object for each class (e.g. `class MyViewEnvironment: ObservableObject`) – Senseful Dec 15 '19 at 23:09
  • @Senseful I don't know if it is possible like you said, but either way I wouldn't create a wrapper. It is much better to be explicit about what you are trying to do - no generics should be needed. Simpler code is almost always better :) – George Dec 15 '19 at 23:11
7

If you want to use the environment system, you need to provide a key to access it. You can't use an unregistered key (which is what your error is). First, create an EnvironmentKey:

struct MyIntEnvironmentKey: EnvironmentKey {
    static var defaultValue = 0
}

Next, create a key path to set/get your environment value:

extension EnvironmentValues {
    var myInt: Int {
        get { self[MyIntEnvironmentKey.self] }
        set { self[MyIntEnvironmentKey.self] = newValue }
    }
}

Now, you can set it in your parent View:

MyView().environment(\.myInt, myInt)

And crucially, you need to reference the key in the view in which you want to reference it:

struct MyView: View {
    @Environment(\.myInt) private var myInt: Int

    var body: some View {
        Text("\(self.myInt)")
    }
}
Procrastin8
  • 4,193
  • 12
  • 25