简体   繁体   中英

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

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

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 {
        ...
    }
}

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.

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()
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM