28

I'm not sure if this is an antipattern in this brave new SwiftUI world we live in, but essentially I have an @EnvironmentObject with some basic user information saved in it that my views can call.

I also have an @ObservedObject that owns some data required for this view.

When the view appears, I want to use that @EnvironmentObject to initialize the @ObservedObject:

struct MyCoolView: View { 

    @EnvironmentObject userData: UserData
    @ObservedObject var viewObject: ViewObject = ViewObject(id: self.userData.UID)  

    var body: some View { 
            Text("\(self.viewObject.myCoolProperty)")
    } 
}

Unfortunately I can't call self on the environment variable until after initialization:

"Cannot use instance member 'userData' within property initializer; property initializers run before 'self' is available."

I can see a few possible routes forward, but they all feel like hacks. How should I approach this?

2
  • Maybe you can try adding a custom init to the struct. Commented Dec 17, 2019 at 3:36
  • I tried that and got a somewhat strange error: Property wrappers are not yet supported on local properties Basically its saying I cant create an @ObservedObject in an init method. Commented Dec 17, 2019 at 3:45

2 Answers 2

32

Here is the approach (the simplest IMO):

struct MyCoolView: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        MyCoolInternalView(ViewObject(id: self.userData.UID))
    }
}

struct MyCoolInternalView: View {
    @EnvironmentObject var userData: UserData
    @ObservedObject var viewObject: ViewObject

    init(_ viewObject: ViewObject) {
        self.viewObject = viewObject
    }

    var body: some View {
            Text("\(self.viewObject.myCoolProperty)")
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This is perfect. The MyCoolView was actually a child to a 'home' view where I declared the ObservedObject. Thanks!
But what if you want to manipulate the userData inside ViewObject without an entirely new ViewObject being created every time?
0

One way to initialize ObservableObject that depends on environment (key or object) is to pass the necessary environment from a parent view via a view init that will itself create the StateObject using wrappedValue: initializer.

Solution
struct FirstScreen: View {
  @Environment(\.fooService) var fooService
  
  var body: some View {
    content
      .sheet(...) {
        SecondScreen(
          // Here we DO have access to environment
          fooService: fooService
        )
      }
  }
}

struct SecondScreen: View {
  @StateObject var viewModel: ViewModel

  init(fooService: FooService) {
    // The ObservableObject initialisation is encapsulated here
    self._viewModel = .init(
      // The autoclosure initializer needs to be used
      wrappedValue: .init(fooService: fooService)
    )
  }
}
Disclosure

Following is a link to my article describing the solution in more detail:

ObservableObject initialisation using Environment

1 Comment

As I commented in another one of your answers, you must explicitly disclose any affiliation you have with sites you link.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.