Search code examples
iosswiftswiftuinavigationview

How to present a View without embedding it into a current navigation flow in SwiftUI?


I have a logging onboarding being finished, and I need to present a HomeView, which knows nothing about previous navigation flow.

var body: some View {
    if viewModel.isValidated {
       destination()
    } else {
       LoadingView()
    }

Doing it this way I have a navigation bar at the top of destination(). I guess I can hide it, but it would still be the same navigation flow and I need to start a new one. How can I achieve that?(iOS 13)


Solution

  • One way to handle this is with an @Environment object created from a BaseViewModel. The way that this works is to essentially control the state of the presented view from a BaseView or a view controller. I'll attempt to simplify it for you the best I can.

    class BaseViewModel: ObservableObject {
        @Published var baseView: UserFlow = .loading
    
        init() {
             //Handle your condition if already logged in, change 
             //baseView to whatever you need it to be. 
        }
        
        enum UserFlow {
            case loading, onboarding, login, home
        }
    }
    

    Once you've setup your BaseViewModel you'll want to use it, I use it in a switch statement with a binding to an @EnvironmentObject so that it can be changed from any other view.

    struct BaseView: View {
        @EnvironmentObject var appState: BaseViewModel
        
        var body: some View {
            Group {
                switch appState.userFlow {
                case .loading:
                    LoadingView()
                case .onboarding:
                    Text("Not Yet Implemented")
                case .login:
                    LandingPageView()
                case .home:
                    BaseHomeScreenView().environmentObject(BaseHomeScreenViewModel())
                }
            }
        }
    }
    

    Your usage, likely at the end of your register/login flow, will look something like this.

    struct LoginView: View {
    
    @EnvironmentObject var appState: BaseViewModel
    
        var body: some View {
            Button(action: {appState = .home}, label: Text("Log In"))
        }
    }
    

    So essentially what's happening here is that you're storing your app flow in a particular view which is never disposed of. Think of it like a container. Whenever you change it, it changes the particular view you want to present. The especially good thing about this is that you can build a separate navigation hierarchy without the use of navigation links, if you wanted.