Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to navigate from a subview of a TabView back to the TabView in SwiftUI?

In SwiftUI a TabView must be the root view. You, therefore, cannot use a NavigationLink to navigate to a TabView. So say for instance I have four screens in my app.

Screen A is a TabView that holds Screen B and Screen C. Screen B is a List that has a NavigationLink to take you to a list item details (Screen D) Screen C is an information view (it's not important in the question) Screen D is a list item details screen, you must first navigate to screen b to get here. Screen D, however, has a button that should perform a network action in a ViewModel, and then take you to ScreenA upon completion.

How can Screen D navigation back two levels to the root screen (Screen A)?

like image 682
Adrian Le Roy Devezin Avatar asked Sep 11 '25 12:09

Adrian Le Roy Devezin


2 Answers

An effective way to "pop" to your root view would be to utilize the isDetailLink modifier on the NavigationLink that is used for navigating.

By default, isDetailLink is true. This modifier is used for a variety of view containers, like on iPad, where the detail view would appear on the right side.

Setting isDetailLink to false means that the view will be pushed on top of the NavigationView stack, and can also be pushed off.

Along with setting isDetailLink to false on NavigationLink, pass the isActive binding to each child destination view. When you want to pop to the root view, set the value to false and it will pop everything off:

import SwiftUI

struct ScreenA: View {
    @State var isActive : Bool = false

    var body: some View {
        NavigationView {
            NavigationLink(
                destination: ScreenB(rootIsActive: self.$isActive),
                isActive: self.$isActive
            ) {
                Text("ScreenA")
            }
            .isDetailLink(false)
            .navigationBarTitle("Screen A")
        }
    }
}

struct ScreenB: View {
    @Binding var rootIsActive : Bool

    var body: some View {
        NavigationLink(destination: ScreenD(shouldPopToRootView: self.$rootIsActive)) {
            Text("Next screen")
        }
        .isDetailLink(false)
        .navigationBarTitle("Screen B")
    }
}

struct ScreenD: View {
    @Binding var shouldPopToRootView : Bool

    var body: some View {
        VStack {
            Text("Last Screen")
            Button (action: { self.shouldPopToRootView = false } ){
                Text("Pop to root")
            }
        }.navigationBarTitle("Screen D")
    }
}
like image 148
Joshua Martinez Avatar answered Sep 14 '25 02:09

Joshua Martinez


I did a trick like this, worked for me.

In SceneDelegate.swift, I modified auto generated code.


let contentView = ContentView()

if let windowScene = scene as? UIWindowScene {
   let window = UIWindow(windowScene: windowScene)
   // Trick here.
   let nav = UINavigationController(
       rootViewController: UIHostingController(rootView: contentView)
    )
    // I embedded this host controller inside UINavigationController
    //  window.rootViewController = UIHostingController(rootView: contentView)
    window.rootViewController = nav
    self.window = window
    window.makeKeyAndVisible()
}

Note: I expected embedding TabView inside NavigationView would do the same job, but did not work, it was the reason of doing this trick.

I assume, contentView is the View that you want to pop to (the one includes TabView)

Then in any view navigated from that view, you can call

extension View {
    func popToRoot() {
        guard let rootNav = UIApplication.shared.windows.first?.rootViewController as? UINavigationController else { return }
        rootNav.popToRootViewController(animated: true)
    }
}

// Assume you eventually came to this view.
struct DummyDetailView: View {

    var body: some View {

        Text("DetailView")
           .navigationBarItems(trailing:
               Button("Pop to root view") {
                   self.popToRoot()
               }
           )
    }
}

// EDIT: Requested sample with a viewModel
struct DummyDetailViewWithViewModel: View {

    var viewModel: SomeViewModel = SomeViewModel()

    var body: some View {        
        Button("Complete Order!!") {
            viewModel.completeOrder(success: { _ in
                print("Order Completed")
                self.popToRoot()
            })
        }
    }
}
like image 37
Enes Karaosman Avatar answered Sep 14 '25 03:09

Enes Karaosman