Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to switch between distinct UI hierarchies with SwiftUI?

Imagine a typical app that has onboarding, sign-in/registration, and content of some kind. When the app loads you need to make some decision about which view to show. A naive implementation may look like this:

struct ContentView: View {
    //assuming some centralized state that keeps track of basic user activity
    @State var applicationState = getApplicationState()

    var body: some View {

        if !applicationState.hasSeenOnboarding {
            return OnBoarding()
        }

        if !applicationState.isSignedIn {
            return Registration()
        }

        return MainContent()
    }
}

Obviously this approach fails because SwiftUI views require an opaque return type of some View. This can be mitigated (albeit hackishly) using the AnyView wrapper type, which provides type erasure and will allow the code below to compile.

struct ContentView: View {
    //assuming some centralized state that keeps track of basic user activity
    @State var applicationState = getApplicationState()

    var body: some View {

        if !applicationState.hasSeenOnboarding {
            return AnyView(OnBoarding())
        }

        if !applicationState.isSignedIn {
            return AnyView(Registration())
        }

        return AnyView(MainContent())
    }
}

Is there a more correct way of doing this that doesn't require the use of AnyView? Is there functionality in the SceneDelegate that can handle the transition to a completely distinct view hierarchy?

like image 303
Robert Avatar asked Sep 04 '19 00:09

Robert


1 Answers

Probably the most SwiftUI-y way to do things like these is by using the Group view:

import SwiftUI

struct ContentView: View {
    @State private var applicationState = getApplicationState()

    var body: some View {
        Group {
            if !applicationState.hasSeenOnboarding {
                OnBoarding()
            } else if !applicationState.isSignedIn {
                Registration()
            } else {
                MainContent()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

The most important thing to note is that, this way, you won't rely on type erasure with AnyView (to avoid if not strictly necessary).

If you want to encapsulate the initial view creation in a method don't use type erasure. Instead, use the some keyword:

import SwiftUI

struct ContentView: View {
    @State private var applicationState = getApplicationState()

    private func initialView() -> some View {
        if !applicationState.hasSeenOnboarding {
            OnBoarding()
        } else if !applicationState.isSignedIn {
            Registration()
        } else {
            MainContent()
        }
    }

    var body: some View {
        initialView()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
like image 70
matteopuc Avatar answered Oct 03 '22 00:10

matteopuc