Search code examples
iosswiftswiftuitabviewtabitem

SwiftUI Disable specific tabItem selection in a TabView?


I have a TabView that presents a sheet after tapping on the [+] (2nd) tabItem. At the same time, the ContentView is also switching the TabView's tab selection, so when I dismiss the sheet that is presented, the selected tab is a blank one without any content. Not an ideal user experience.

My question:

I am wondering how I can simply disable that specific tabItem so it doesn't "behave like a tab" and simply just present's the sheet while maintaining the previous tab selection prior to tapping the [+] item. Is this possible with SwiftUI or should I got about this another way to achieve this effect?

Image of my tab bar:

enter image description here

Here's the code for my ContentView where my TabView is:

struct SheetPresenter<Content>: View where Content: View {
    @EnvironmentObject var appState: AppState
    @Binding var isPresenting: Bool

    var content: Content
    var body: some View {
        Text("")
            .sheet(isPresented: self.$isPresenting, onDismiss: {
                // change back to previous tab selection
                print("New listing sheet was dismissed")

            }, content: { self.content})
            .onAppear {
                DispatchQueue.main.async {
                    self.isPresenting = true
                    print("New listing sheet appeared with previous tab as tab \(self.appState.selectedTab).")
                }
            }
    }
}

struct ContentView: View {
    @EnvironmentObject var appState: AppState
    @State private var selection = 0
    @State var newListingPresented = false

    var body: some View {

        $appState.selectedTab back to just '$selection'
        TabView(selection: $appState.selectedTab){

            // Browse
            BrowseView()
                .tabItem {
                    Image(systemName: (selection == 0 ? "square.grid.2x2.fill" : "square.grid.2x2")).font(.system(size: 22))
                }
                .tag(0)


            // New Listing
            SheetPresenter(isPresenting: $newListingPresented, content: NewListingView(isPresented: self.$newListingPresented))
                .tabItem {
                    Image(systemName: "plus.square").font(.system(size: 22))
                }
                .tag(1)

            // Bag
            BagView()
                .tabItem {
                    Image(systemName: (selection == 2 ? "bag.fill" : "bag")).font(.system(size: 22))
                }
                .tag(2)

            // Profile
            ProfileView()
                .tabItem {
                    Image(systemName: (selection == 3 ? "person.crop.square.fill" : "person.crop.square")).font(.system(size: 22))
                }
                .tag(3)
        }.edgesIgnoringSafeArea(.top)
    }
}

And here's AppState:

final class AppState: ObservableObject {

    @Published var selectedTab: Int = 0
}

Solution

  • You are pretty close to what you want to achieve. You will just need to preserve the previous selected tab index and reset the current selected tab index with that preserved value at the time of the dismissal of the sheet. That means:

    .sheet(isPresented: self.$isPresenting, onDismiss: {
        // change back to previous tab selection
        self.appState.selectedTab = self.appState.previousSelectedTab
    }, content: { self.content })
    

    So how do you keep track of the last selected tab index that stays in sync with the selectedTab property of the AppState? There may be more ways to do that with the APIs from Combine framework itself, but the simplest solution that comes to my mind is:

    final class AppState: ObservableObject {
        // private setter because no other object should be able to modify this
        private (set) var previousSelectedTab = -1 
        @Published var selectedTab: Int = 0 {
            didSet {
                previousSelectedTab = oldValue
            }
        }
    }
    

    Caveats:

    The above solution of may not be the exact thing as disable specific tab item selection but after you dismiss the sheet it will revert back with a soothing animation to the selected tab prior to presenting the sheet. Here is the result.