SwiftUI 2 pop to root view with no Scene delegate
A possible solution is to create an ObservableObject
that is responsible for routing:
enum AppView {
case login, main
}
class ViewRouter: ObservableObject {
// here you can decide which view to show at launch
@Published var currentView: AppView = .login
}
Then, you can inject this ViewRouter
as an EnvironmentObject
to the root view:
@main
struct TestApp: App {
@StateObject private var viewRouter = ViewRouter()
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(viewRouter)
}
}
}
Now you can change the current view from any view:
struct RootView: View {
@EnvironmentObject private var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentView == .login {
LoginView()
} else if viewRouter.currentView == .main {
MainView()
}
}
}
}
struct LoginView: View {
@EnvironmentObject private var viewRouter: ViewRouter
var body: some View {
VStack {
Text("Login View")
Button("Log in") {
viewRouter.currentView = .main
}
}
}
}
struct MainView: View {
@EnvironmentObject private var viewRouter: ViewRouter
var body: some View {
VStack {
Text("Main View")
Button("Log out") {
viewRouter.currentView = .login
}
}
}
}
How can I pop to the Root view using SwiftUI?
Setting the view modifier isDetailLink
to false
on a NavigationLink
is the key to getting pop-to-root to work. isDetailLink
is true
by default and is adaptive to the containing View. On iPad landscape for example, a Split view is separated and isDetailLink
ensures the destination view will be shown on the right-hand side. Setting isDetailLink
to false
consequently means that the destination view will always be pushed onto the navigation stack; thus can always be popped off.
Along with setting isDetailLink
to false
on NavigationLink
, pass the isActive
binding to each subsequent destination view. At last when you want to pop to the root view, set the value to false
and it will automatically pop everything off:
import SwiftUI
struct ContentView: View {
@State var isActive : Bool = false
var body: some View {
NavigationView {
NavigationLink(
destination: ContentView2(rootIsActive: self.$isActive),
isActive: self.$isActive
) {
Text("Hello, World!")
}
.isDetailLink(false)
.navigationBarTitle("Root")
}
}
}
struct ContentView2: View {
@Binding var rootIsActive : Bool
var body: some View {
NavigationLink(destination: ContentView3(shouldPopToRootView: self.$rootIsActive)) {
Text("Hello, World #2!")
}
.isDetailLink(false)
.navigationBarTitle("Two")
}
}
struct ContentView3: View {
@Binding var shouldPopToRootView : Bool
var body: some View {
VStack {
Text("Hello, World #3!")
Button (action: { self.shouldPopToRootView = false } ){
Text("Pop to root")
}
}.navigationBarTitle("Three")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
How can I change the root view in a SwiftUI app (no SceneDelegate or AppDelegate)?
In order for your code to update, SwiftUI has to know to look for changes. Most commonly, this is done with an @ObservableObject
or @StateObject
. Right now, you just have a regular property called myController
-- SwiftUI won't know to respond to changes.
You didn't give any information about what MyController is, but I can make some assumptions and turn it into an ObservedObject:
class MyController : ObservableObject {
static let shared = MyController()
@Published var isUserFullscreen = false
}
Then, in your view, you can redefine your property as:
@ObservedObject var myController = MyController.shared
Then, your view should know to respond to changes on the @Published property of the @ObservableObject.
Here's some more reading on @ObservableObject : https://www.hackingwithswift.com/quick-start/swiftui/observable-objects-environment-objects-and-published
SwiftUI go back to RootView in sceneWillEnterForeground SceneDelegate.swift
There is no "go back" but the possible approach is to recreate root view controller, by moving "by default" generated content creation into other delegate method as below...
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
self.window = window
}
}
func sceneWillEnterForeground(_ scene: UIScene) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let contentView = RootView().environment(\.managedObjectContext, context)
let singleOrder = SingleOrder()
window?.rootViewController = UIHostingController(rootView: contentView.environmentObject(singleOrder))
window?.makeKeyAndVisible()
}
Change the root view of UIHostingController in SwiftUI
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.
Related Topics
How to Make Swiftui Uiviewrepresentable View Hug Its Content
Why Do I Get "Static Member '...' Cannot Be Used on Instance of Type '...'" Error
Alamofire 5 Upload Encodingcompletion
Swiftui MACos Scroll a List with Arrow Keys While a Textfield Is Active
What Is the Slice Compare Logic in Swift
Having Trouble with Nstimer (Swift)
How to I Turn Off the Ambient Light in Scene Kit (With Swift)
Different Colors for Bars in Barchart Depend on Value
What Does a "Do Statement" Without Catch Block Mean
Swiftui Tabbar: Action for Tapping Tabitem of Currently Selected Tab to Reset View
How to Add Floating Button on Top of the Uitableview
Create a Swift Dictionary Subclass
How to Turn Off Color Literals in Xcode
Cannot Subscript a Value of Type '[String:String]' with an Index of Type 'String'
Refreshing Auth Token with Moya
Is This Safe to Call Wait() of Dispatchsemaphore Several Times at One Time
How to Initialise a New Nsdocument Instance in Swift
Swift: How to Hold Any Possible Instance of a Generic Type in a Variable