How do you hide the NavBar in certain SwiftUI Views while also proving a Back button to go to the previous tab?

225 Views Asked by At

I am trying to recreate the NavBar behavior that a lot of fitness apps like Strava and AllTrails use to make the activity specific page less busy by removing the NavBar for that specific page.

In AllTrails for example, regardless of which view you are in, if you click on "Navigation" it will remove the NavBar and take you to the activity view providing a back button arrow that takes you to whatever Tab view you were in before you pressed that button. It even remembers what spot you were in on the previous page (if that was. page with a list) meaning that it doesn't refresh the View on the back button but rather is somehow saving the state.

enter image description here

You can see, Strava does the same:

enter image description here

I was able to somewhat replicate a little bit of this behavior using Navigation Stacks:

import SwiftUI
import CoreData
import Foundation

enum Tab {
    case Home
    case Activity
    case PastSessions
}

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    @State var selectedTab: Tab = .Home
        
    var body: some View {
        NavigationStack {
            TabView(selection: $selectedTab) {
                HomeView()
                .tabItem {
                    Image(systemName: "house")
                    Text("Home")
                }
                .tag(Tab.Home)
                
                NavigationView {
                    CurrentSessionView()
                        .toolbar {
                            ToolbarItem(placement: .navigationBarLeading) {
                                Button {
                                    selectedTab = Tab.Home
                                } label: {
                                    HStack {
                                        Image(systemName: "chevron.backward")
                                        Text("Back")
                                    }
                                }
                            }
                        }
                        .toolbar(.hidden, for: .tabBar)
                }
                .tabItem {
                    Image(systemName: "figure.climbing")
                    Text("Activity")
                }
                .tag(Tab.Activity)
                
                [...]
            }
        }
    }
}

The issue is that the Back button here always goes back to the main Home page and for some reason the NavBar only hides on the first time you click the Activity page and navigate "Back". Once you navigate back once, every time you reopen the Activity page it will always show the NavBar until you quit the app and reopen it.

I was going to fix the Back button going to the proper previous page using a variable to remember the last tab before the Activity tab, but I can not figure out what is causing this weird NavBar issue.

I've also tried passing the selectedTab variable to the child view as a Binding object and having it modify it directly, but that did not fix the issue.

enter image description here

Similar, but not quite the same, StackOverflow posts I've references so far:

I'd really appreciate any tips on how to go about this. Thanks!

1

There are 1 best solutions below

1
On BEST ANSWER

In the examples you show, there's no stack view navigation at all, but rather just a presented sheet/cover. Which makes a lot of sense if you do not want to mess up you navigation state when dismissing the sheet. The only issue is that TabViews do not support buttons so you can trigger a value to display the sheet (out of the box). You can have a look at Tabbar middle button utility function in SwiftUI to emulate this behaviour. Using the method in the accepted answer for that question:

import SwiftUI
import Combine

enum Tab {
    case Home
    case Activity
    case PastSessions
}

struct ContentView: View {
    @State private var selectedTab: Tab = .Home
    @State private var previousSelectedTab: Tab = .Home
    
    @State private var isShowingSheet = false

    var body: some View {
        TabView(selection: $selectedTab) {
            Text("Home View")
                .tabItem {
                    Image(systemName: "house")
                    Text("Home")
                }
                .tag(Tab.Home)
            
            Text("")
                .tabItem {
                    Image(systemName: "figure.climbing")
                    Text("Activity")
                }
                .tag(Tab.Activity)
                .onAppear {
                    isShowingSheet = true
                    // restore tab
                    selectedTab = previousSelectedTab
                }
            
            Text("Past Sessions View")
                .tabItem {
                    Image(systemName: "clock")
                    Text("Past Sessions")
                }
                .tag(Tab.PastSessions)
        }
        .onReceive(Just(selectedTab)) { value in
            if value != Tab.Activity {
                previousSelectedTab = value
            }
        }
        .fullScreenCover(isPresented: $isShowingSheet) {
            // Only to allow for the top bar (back button)
            // not used for navigation
            NavigationStack {
                Text("Full Page Content")
                    .toolbar {
                        ToolbarItem(placement: .topBarLeading) {
                            Button {
                                isShowingSheet = false
                            } label: {
                                HStack {
                                    Image(systemName: "chevron.backward")
                                    Text("Back")
                                }
                            }
                        }
                    }
            }
        }
    }
}