Search code examples
swiftuicombineobservableobject

Reference EnvironmentObject in ObservableObject


I have a LoginView that shows a RegisterView if the user is not logged in, and a ContentView if he is logged in:

struct LoginView: View {
    @EnvironmentObject var userManager: UserManager
    var body: some View {
        Group {
            if userManager.isRegistered {
                ContentView()
            } else {
                RegisterView()
            }
        }
    }
}

ContentView have three ObservedObject properties, that uses combine to fetch content from a server with rest api's.

struct ContentView: View {
    @EnvironmentObject var userManager: UserManager
    @StateObject var usersStore = UsersStore()
    @StateObject var rolesStore = RolesStore()
    @StateObject var elementsStore = ElementsStore()

    var body: some View {
        NavigationView {
            ZStack {
                Image("stell")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .opacity(0.1)
            
                VStack(alignment: .leading, spacing: 5) {
                    NavigationLink(destination: UsersView(usersStore: usersStore) ) {
                        Text("Users")
                    }
                    NavigationLink(destination: RolesView(rolesStore: rolesStore)) {
                        Text("Roles")
                    }
                    NavigationLink(destination: ElementsView(elements: $elementsStore.elements)) {
                        Text("Elements")
                    }
                }.font(.title).padding(20)
            }.navigationBarTitle(Text("STELL"))
        }
    }
}

The problem I have is that I want to reference userManager from any of the observedObjects, e.g. when the rest api's returns 401 Unauthorized when the session token has expired. Then I want the ObservedObject to set the isRegistered flag in userManager to false so the RegisterView is automatically shown. But how can I do that? I can't set a reference to userManager in any of the ObservedObject property initializers, because the compiler complains about property initializers is run before self is available.


Solution

  • I would use in this case dependency injection via constructor... below is show possible approach on example of UsersStore, for others it would be the same

    Changes in UsersStore

    class UsersStore: ObservableObject {
        var manager: UserManager
        
        init(manager: UserManager) { // << inject UserManager via constructor
            self.manager = manager 
        }
        ...
    }
    

    Changes in ContentView

    struct ContentView: View {
        @EnvironmentObject var userManager: UserManager
        @ObservedObject var usersStore: UsersStore
    
        init(usersStore: UsersStore) {
            self.usersStore = usersStore // << inject UsersStore via contructor
        }
    

    Changes in usage

    struct LoginView: View {
        @EnvironmentObject var userManager: UserManager
        var body: some View {
            Group {
                if userManager.isRegistered {
                    // userManager is valid here some UsersStore can be created
                    ContentView(usersStore: UsersStore(manager: self.userManager))