Search code examples
swiftswiftui

how do i hide a custom tab bar in subviews


I am learning about swiftUI navigation stack and Tab Bars, most tutorials just show implementation of the tab bars. i am trying to hide my custom tab bar in subviews. Please can someone explain what to do so that I can learn and take note of it for future references. Thanks in advance

struct MainTabbedView: View {
    
    @State var selectedTab = 0
    @Environment(\.isTabBarVisible) var isTabBarVisible
    
    var body: some View {
        ZStack(alignment: .bottom) {
            TabView(selection: $selectedTab) {
                HomeView()
                    .tag(0)

                FavoriteView()
                    .tag(1)

                FantasyView_Preview()
                    .tag(2)

                ProfileView()
                    .tag(3)
            }

            if isTabBarVisible {
                ZStack {
                    HStack {
                        ForEach((TabbedItems.allCases), id: \.self) { item in
                            Button {
                                selectedTab = item.rawValue
                            } label: {
                                CustomTabItem(imageName: item.iconName, title: item.title, isActive: (selectedTab == item.rawValue))
                            }
                        }
                    }
                    .padding(6)
                }
                .frame(height: 70)
                .background(Color.yinmnBlue.opacity(0.2))
                .cornerRadius(35)
                .padding(.horizontal, 26)
            }
        }
extension MainTabbedView {
    func CustomTabItem(imageName: String, title: String, isActive: Bool) -> some View {
        HStack(spacing: 10) {
            Spacer()
            Image(systemName:imageName)
                .resizable()
                .renderingMode(.template)
                .foregroundColor(isActive ? .black : .gray)
                .frame(width: 25, height: 25)
            if isActive {
                Text(title)
                    .font(.system(size: 14))
                    .foregroundColor(isActive ? .black : .gray)
            }
            Spacer()
        }
        .frame(width: isActive ? .infinity : 60, height: 60)
        .background(isActive ? Color.yinmnBlue.opacity(0.4) : .clear)
        .cornerRadius(30)
    }
}

HomeView.swift

struct HomeView: View {
    @State private var showsubview = false
    var body: some View {
        NavigationStack{
            Button(action: {
                showsubview = true
            }, label: {
                Text("Go to sub view")
            })
            .navigationDestination(isPresented: $showsubview) {
                navigation1(isPresented: $showsubview)
                    .environment(\.isTabBarVisible, false)
            }
        }
    }
}

This is the screenshot first screen

This is a screenshot of the subview

i tried to use environment variables but it did not work

struct TabBarVisibilityKey: EnvironmentKey {
    static let defaultValue: Bool = true
}

extension EnvironmentValues {
    var isTabBarVisible: Bool {
        get { self[TabBarVisibilityKey.self] }
        set { self[TabBarVisibilityKey.self] = newValue }
    }
}

Solution

  • Environment values are for passing information down the view hierarchy, but in this case you want subviews to inform their parents of whether they want the tab bar to be shown. You should use a preference instead.

    First, write a preference key

    struct CustomTabBarVisiblePreference: PreferenceKey {
        // nil means "not set"
        static let defaultValue: Bool? = nil
        
        static func reduce(value: inout Bool?, nextValue: () -> Bool?) {
            guard let next = nextValue() else { return }
            value = next
        }
    }
    

    In the navigation destination of HomeView, set the preference to false:

    struct HomeView: View {
        @State private var showsubview = false
        var body: some View {
            NavigationStack{
                Button(action: {
                    showsubview = true
                }, label: {
                    Text("Go to sub view")
                })
                .navigationDestination(isPresented: $showsubview) {
                    Text("Destination")
                        // here:
                        .preference(key: CustomTabBarVisiblePreference.self, value: false)
                }
            }
        }
    }
    

    In the tab view, use a overlayPreferenceValue instead of a ZStack to overlay the tab bar. This way, you can read the preference value and determine whether to show a tab bar.

    struct MainTabbedView: View {
        
        @State var selectedTab = 1
        
        var body: some View {
            TabView(selection: $selectedTab) {
                HomeView()
                    .tag(0)
                
                // I have replaced other views with simple Texts so that this is a minimal reproducible example
                Text("Favourites View")
                    .tag(1)
                
                Text("Fantasy View")
                    .tag(2)
                
                Text("Profile View")
                    .tag(3)
            }
            .overlayPreferenceValue(CustomTabBarVisiblePreference.self, alignment: .bottom) { visible in
                // "!= false" here so that the tab bar is visible if the preference is not set
                if visible != false {
                    ZStack {
                        HStack {
                            ForEach(0..<4, id: \.self) { item in
                                Button {
                                    selectedTab = item
                                } label: {
                                    CustomTabItem(imageName: "circle", title: "Title \(item)", isActive: selectedTab == item)
                                }
                            }
                        }
                        .padding(6)
                    }
                    .frame(height: 70)
                    .background(Color.blue.opacity(0.2))
                    .cornerRadius(35)
                    .padding(.horizontal, 26)
                }
            }
        }
    }
    
    // I refactored CustomTabItem as its own View struct
    struct CustomTabItem: View {
        let imageName: String
        let title: String
        let isActive: Bool
        
        var body: some View {
            HStack(spacing: 10) {
                Spacer()
                Image(systemName:imageName)
                    .resizable()
                    .renderingMode(.template)
                    .foregroundColor(isActive ? .black : .gray)
                    .frame(width: 25, height: 25)
                if isActive {
                    Text(title)
                        .font(.system(size: 14))
                        .foregroundColor(isActive ? .black : .gray)
                }
                Spacer()
            }
            .frame(width: isActive ? nil : 60, height: 60)
            .frame(maxWidth: isActive ? .infinity : nil)
            .background(isActive ? Color.blue.opacity(0.4) : .clear)
            .cornerRadius(30)
        }
    }