SwiftUI @EnvironmentObject error: may be missing as an ancestor of this view -- accessing object in the init()

970 views Asked by At

The following code produces the runtime error: @EnvironmentObject error: may be missing as an ancestor of this view. The tState in the environment is an @ObservedObject.

struct TEditorView: View {
    @EnvironmentObject private var tState: TState
    
    @State var name = ""
    
    init() {
        self._name = State(initialValue: tState.name)
    }
 
    var body: some View {
        ...
    }
}

XCode 12.0.1 iOS 14

3

There are 3 answers

1
LostInTheTrees On BEST ANSWER

The answer is that an Environment Object apparently cannot be accessed in an init() function. However, an ObservedObject can be. So I changed the code to this and it works. To make it easy I turned TState into a singleton that I could access anywhere. This could probably replace the use of @EnvironmentObject in many situations.

struct TEditorView: View {
    @ObservedObject private var tState = TState.shared
    //@EnvironmentObject private var tState: TState
    
    @State var name = ""
    
    init() {
        self._name = State(initialValue: tState.name)
    }
 
    var body: some View {
        ...
    }
}

0
Claus Jørgensen On

A different approach here could be to inject the initial TState value in the constructor and do-away with the @EnvironmentObject completely. Then from the parent view you can use the @EnvironmentObject value when creating the view.

struct TEditorView: View {
    @State var name = ""
    
    init(tState: TState) {
        self._name = State(initialValue: tState.name)
    }
 
    var body: some View {
        ...
    }
}

struct ContentView: View {
    @EnvironmentObject private var tState: TState
    
    var body: some View {
        TEditorView(state: tState)
    }
}

Or use a @Binding instead of @State if the name value is meant to be two-way.

In general I'd also question why you need the @EnvironmentObject in the constructor. The idea is with a @EnvironmentObject is that it's represented the same in all views, so you should only need it body.

If you need any data transformations it should be done in the object model itself, not the view.

0
lorem ipsum On

The @State should be set as private and per the documentation should only be accessed in the View body.

https://developer.apple.com/documentation/swiftui/state

An @EnvironmentObject should be set using the ContentView().environmentObject(YourObservableObject)

https://developer.apple.com/documentation/combine/observableobject https://developer.apple.com/documentation/swiftui/stateobject

Below is some Sample code

import SwiftUI
class SampleOO: ObservableObject {
    @Published var name: String = "init name"
}
//ParentView
struct OOSample: View {
    //The first version of an @EnvironmentObject is an @ObservedObject or @StateObject
    //https://developer.apple.com/tutorials/swiftui/handling-user-input
    @ObservedObject var sampleOO: SampleOO = SampleOO()
    var body: some View {
        VStack{
            Button("change-name", action: {
                self.sampleOO.name = "OOSample"
            })
        
        Text("OOSample = " + sampleOO.name)
        //Doing this should fix your error code with no other workarounds
        ChildEO().environmentObject(sampleOO)
        SimpleChild(name: sampleOO.name)
        }
    }
}
//Can Display and Change name
struct ChildEO: View {
    @EnvironmentObject var sampleOO: SampleOO
    var body: some View {
        VStack{
            //Can change name
            Button("ChildEO change-name", action: {
                self.sampleOO.name = "ChildEO"
            })
            Text("ChildEO = " + sampleOO.name)
        }
    }
}
//Can only display name
struct SimpleChild: View {
    var name: String
    var body: some View {
        VStack{
            //Cannot change name
            Button("SimpleChild - change-name", action: {
                print("Can't change name")
                //self.name = "SimpleChild"
            })
            Text("SimpleChild = " + name)
        }
    }
}

struct OOSample_Previews: PreviewProvider {
    static var previews: some View {
        OOSample()
    }
}