3

Is it possible to use EnvironmentObject in the app file for a new SwiftUI 2.0 project?
I'm using the following code and I get the error:

Fatal error: No ObservableObject of type Authentication found. A View.environmentObject(_:) for Authentication may be missing as an ancestor of this view.: file SwiftUI, line 0

I'm assuming this error is because I haven't used the .environmentObject modifier in any view yet and so the UserAuth has not yet been placed in the environment.

But I want this loginStatus to be available anywhere in my app and to be able to use the function getLoginStatus also anywhere in my app on one common instance of the UserAuth Class.

I was hoping to put this in my rootView and have it apply to all views but not sure how to do this with the structure I have below in my app file.

enum LoginStatus {
    case signedIn
    case signedOut
}

import SwiftUI

@main
struct ExampleApp: App {

    @EnvironmentObject var userAuth: UserAuth

    init() {
        userAuth.getLoginStatus()
    }
    
    var body: some Scene {
        WindowGroup {
            switch userAuth.loginStatus {
            case .signedIn:
                Text("Signed In")
            case .signedOut:
                Text("Signed Out")
            }
        }
    }
}

import SwiftUI

class UserAuth: ObservableObject {
    
    @Published var loginStatus: LoginStatus = .undetermined
    
    func getLoginStatus() {
        // asynchrounously set loginStatus
    }
}
alionthego
  • 8,508
  • 9
  • 52
  • 125
  • For that why don't you make Singleton class to access your data from anywhere in App to whole lifecycle. – Kudos Mar 25 '21 at 07:55

2 Answers2

3

You can use it as regular property at that level and pass as environment down into view hierarchy if/when needed, like

struct ExampleApp: App {

    let userAuth = UserAuth()     // << here !!
    
    var body: some Scene {
        WindowGroup {
            switch userAuth.loginStatus {
            case .signedIn:
                ContentView()
                   .environmentObject(userAuth)    // << here !!
            case .signedOut:
                Text("Signed Out")
            }
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • the problem with this approach is that I would need to pass it around to all the other views to make it global with I really don't want to do if I can avoid that. I want there to be only one instance of this class or else the loginStatus variable wouldn't make sense. – alionthego Mar 25 '21 at 08:21
  • 2
    1) The instance is the only one because it is, as you see, at App level. 2) Injecting it via `environmentObject(userAuth)` make that instance available for all sub-view hierarchy, if you have many views inside `WindowGroup` just move its content into separated view and inject environment object to that one view (which is most usually is a root ContentView). – Asperi Mar 25 '21 at 08:48
  • I see. that makes sense and I'm very anxious to get it working. my only problem with this now is that app does not observe changes to the loginStatus var in userAuth. Is there anyway to make that happen? – alionthego Mar 25 '21 at 09:18
  • 1
    can I change let userAuth = UserAuth() to @ObservedObject var userAuth = UserAuth() – alionthego Mar 25 '21 at 09:20
  • it seems that works. I've changed it to ObservedObject in the app file and everything works very well. thanks very much for the explanation – alionthego Mar 25 '21 at 09:25
  • I've updated the question with the init to reflect that in case others are doing the same thing. – alionthego Mar 25 '21 at 09:36
1

You need to inject UserAuth from root view, and access your object in View with @EnvironmentObject property.

import SwiftUI

@main
struct ExampleApp: App {

    var body: some Scene {
        WindowGroup {
            MainView().environmentObject(UserAuth())
        }
    }
}


class UserAuth: ObservableObject {
    
    @Published var loginStatus: LoginStatus = .undetermined
    
    func getLoginStatus() {
        // asynchrounously set loginStatus
    }
    
    enum LoginStatus {
        case undetermined
    }
}

struct MainView:View{
    var body: some View{
        Text("")
    }
}

struct MainView1:View{
    
    @EnvironmentObject var userAuth: UserAuth
    
    var body: some View{
        Text("")
    }
}
Tushar Sharma
  • 2,839
  • 1
  • 16
  • 38
  • I'm familiar with this approach and mentioned it in the question. I can add the environmentObject to any other view. I just want to know if there is a way to add it to the WindowGroup or somehow make it available to all views in the app. In the above structure there are basically two main views. One for the signed in fork and one for the not signed in fork. Using your approach the main view is one fork. – alionthego Mar 25 '21 at 08:27