Reputation: 2974
For a new SwiftUI
iOS app, I do the following in the SceneDelegate
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
if Auth().token == nil {
window.rootViewController = UIHostingController(rootView: StartRegistrationView())
} else {
window.rootViewController = UIHostingController(rootView: MainTabbedView())
}
self.window = window
window.makeKeyAndVisible()
}
When a user hasn't signed up or logged in they are taken to the registration flow.
Once a user has signed up, how can I switch the RootView to go to my TabView? I can't seem to find any solution using SwiftUI
.
Should I instead use an Environment
object and listen for changes to the User's Auth
Status?
Upvotes: 27
Views: 21551
Reputation: 605
A little update to this answer you can also achieve the following by doing this :
You can make a Router class
First create an extension to UIApplication
import Foundation
import UIKit
extension UIApplication {
var keyWindow: UIWindow? {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first(where: { $0 is UIWindowScene })
.flatMap({ $0 as? UIWindowScene })?.windows
.first(where: \.isKeyWindow)
}
}
To get the key window.
Then Create a Router class and add these methods :
import Foundation
import SwiftUI
final class Router {
//MARK: Main flow.
public static func showMain(window: UIWindow? = nil) {
Router.setRootView(view: MainView(), window: window)
}
//MARK: private
private static func setRootView<T: View>(view: T, window: UIWindow? = nil) {
if window != nil {
window?.rootViewController = UIHostingController(rootView: view)
UIView.transition(with: window!,
duration: 0.3,
options: .transitionCrossDissolve,
animations: nil,
completion: nil)
return
}else {
UIApplication.shared.keyWindow?.rootViewController = UIHostingController(rootView: view)
UIView.transition(with: UIApplication.shared.keyWindow!,
duration: 0.3,
options: .transitionCrossDissolve,
animations: nil,
completion: nil)
}
}
}
You can now call it from anywhere in your code for example from SceneDelegate :
Router.showMain(window: window)
Or without passing a window
Router.showMain() //if the window is nil it will go to the else statement and use UIApplication.keyWindow.
And the transition will be animated.
Older Answer :
import Foundation
import UIKit
import SwiftUI
class Router {
class var window: UIWindow? {
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
if let sceneDelegate = scene.delegate as? SceneDelegate {
let window = UIWindow(windowScene: scene)
sceneDelegate.window = window
window.makeKeyAndVisible()
return window
}
}
return nil
}
static func showMain() {
window?.rootViewController = UIHostingController(rootView: ContentView())
}
}
Usage :
Router.showMain()
And with this you can decide which window you want as your root at any given time.
Upvotes: 7
Reputation: 13514
In newer Xcode there are some SwiftUI template changes and below is how your initial view is loaded. Refer https://developer.apple.com/documentation/swiftui/app
@main
struct AuthCheckApp: App {
var body: some Scene {
WindowGroup {
WelcomeView()
}
}
}
So in this case the first view is WelcomeView
and this view is responsible to navigate to correct view it might be login, home
struct WelcomeView: View {
@ObservedObject private var auth = Auth()
var body: some View {
if auth.token != nil {
HomeView()
} else {
SignUpView(auth: auth)
}
}
}
Auth is a class confirming to ObservableObject
protocol and has a published property called token. So when this token has a value it will load the HomeView
in above case and in case of nil it will open the SignUpView
.
class Auth: ObservableObject {
@Published var token: String?
}
struct SignUpView: View {
let auth: Auth
var body: some View {
VStack {
Text("Hello, please click below button to login")
.padding()
Button("Login") {
print("Loogin Tapped")
auth.token = "TOKEN"
}
}
}
}
struct HomeView: View {
var body: some View {
Text("Welcome Parth!")
.padding()
.background(Color.red)
}
}
This approach will be helpful in case if you have api dependency and need to wait for the token then you can use the WelcomeScreen
as some splash or animation.
Upvotes: 1
Reputation: 1950
For some animation while changing the rootview use the below code in sceneDelegate:
window.rootViewController = UIHostingController(rootView: HomeView())
// A mask of options indicating how you want to perform the animations.
let options: UIView.AnimationOptions = .transitionCrossDissolve
// The duration of the transition animation, measured in seconds.
let duration: TimeInterval = 0.3
// Creates a transition animation.
UIView.transition(with: window, duration: duration, options: options, animations: {}, completion:
{ completed in
// maybe do something on completion here
})
Upvotes: 1
Reputation: 91
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
if let windowScenedelegate = scene?.delegate as? SceneDelegate {
let window = UIWindow(windowScene: scene!)
window.rootViewController = UIHostingController(rootView:ContentView())
windowScenedelegate.window = window
window.makeKeyAndVisible()
}
By using this we can change the rootView in any button click by implementing the above code.
Upvotes: 9
Reputation: 2378
Very good answer LuLugaga, updated if you don't want to use @Observablebject so will not keep updating all the time, you can use Subject, as soon as you update token String, RootView will update.
struct RootView: View {
var loginViewModel: LoginViewModel = LoginViewModel()
@State var tokenString = ""
var body: some View {
Group {
tokenString.count > 0 ? AnyView(ContentView(model: playerViewModel)) : AnyView(LoginView(loginViewModel: loginViewModel))
}.onReceive(loginViewModel.tokenString) {
self.tokenString = $0
}
}
}
class LoginViewModel {
let tokenString = PassthroughSubject<String, Never>()
var token: String {
get { return "" }
set {
self.tokenString.send(newValue)
}
}
Upvotes: 4
Reputation: 14388
Declare an AppRootView, something like this:
struct AppRootView: View {
@ObservedObject private var auth: Auth
var body: some View {
Group {
if auth.token != nil {
MainTabbedView()
} else {
StartRegistrationView()
}
}
}
}
and then in SceneDelegate set it as the root view:
window.rootViewController = UIHostingController(rootView: AppRootView(auth: $auth))
You have to bind your view to your Auth() either by passing it in as I did above or by setting it on your environment. The beauty of SwiftUI is that as soon as the token is not nil, the view will redraw and your user will find them selves in MainTabbedView.
Upvotes: 40