Reputation: 1145
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
Upvotes: 1
Views: 1137
Reputation: 29242
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()
}
}
Upvotes: 0
Reputation: 26347
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.
Upvotes: 0
Reputation: 1145
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 {
...
}
}
Upvotes: 2