swiftuiuikitswiftui-navigationviewswiftui-tabview

SwiftUI how to hide "more" navigation bar in 5th,6th tabs in a custom Tabview


I'm using a custom tabview to show 6 tabs in the tabview. It's only limited to 5 so if you add 6, it creates a "More" section and the 5th and 6th tabs are shown in a navigation view. When you view 5th,6th tabs, it shows a back navigation bar on top with "more". I got the tabview to show all 6 tabs without "more" option but when I view 5th, 6th tabs it still show a navigation bar on top with "more" back button. X has implemented this with 6th tabs without the extra navigation bar on the 5,6 tabs, so it's certainly possible.

Ideally I'm trying to hide the "more "navigation bar from the first view and keep the navigation bar of the actual view.

I have tried

                     View6()
                            .tag(Tab.home5)
                            .navigationBarTitle("")
                            .navigationBarBackButtonHidden(true)
                            .navigationBarHidden(true)

Tabview

         VStack(spacing: 0){
                 TabView(selection: $currentTab) {
                     View1()
                            .tag(Tab.home1)
                     View2()
                            .tag(Tab.home2)
                     View3()
                            .tag(Tab.home3)
                     View4()
                            .tag(Tab.home4)
                     View5()
                            .tag(Tab.home5)
                     View6()
                            .tag(Tab.home6)

                 }
 
                         TabBar()
                         .toolbar(.hidden, for: .tabBar)
                 
             }

Custom Tab builder (shows all 6 tabs instead of a more section on the 5th tab)

    @ViewBuilder
    func TabBar()->some View{
        
        HStack(spacing: 0){
                    ForEach(Icons){icon in
                        
                        let tabColor: SwiftUI.Color = currentTab == icon.tabIcon ? (scheme == .dark ? .white : .black) : .gray.opacity(0.6)
                        
                        ResizableIconView(IconView: icon.View,color: tabColor)
                            .aspectRatio(contentMode: .fit)
                            .frame(width: 30, height: 30)
                            .frame(maxWidth: .infinity)
                            .contentShape(Rectangle())
                            }
                    }
                }
                .padding(.horizontal)
                .padding(.vertical,10)
                .background {
                    (scheme == .dark ? Color.black : Color.white)
                        .ignoresSafeArea()
                }
    }

When I view 5th,6th tabs, extra more navigation is shown as below, more navigation bar

I tried setting navigationBarBackButtonHidden to true but it doesn't work. I've also tried hiding the navigation bar globally but this removes all navigation bars from alll tabs.

extension UINavigationController {
    open override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        navigationBar.isHidden = true
    }
}


I think the solution is UIKit related


Solution

  • Yes, the answer is basically UIKit. You should wrap a UITabBarController and set its moreNavigationController.isNavigationBarHidden to true.

    I have combined this answer that shows a simple wrapper around a UITabBarController with this answer about the correct timing to set isNavigationBarHidden:

    class CustomTabBarController: UITabBarController, UINavigationControllerDelegate {
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            moreNavigationController.delegate = self
        }
        
        func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
            // hide the nav bar here:
            navigationController.isNavigationBarHidden = true
        }
    }
    
    struct TabBarController: UIViewControllerRepresentable {
        var controllers: [UIViewController]
    
        @Binding var switchTab: Int
    
        func makeUIViewController(context: Context) -> CustomTabBarController {
            let tabBarController = CustomTabBarController()
            tabBarController.tabBar.isHidden = true // hide the tab bar here
            tabBarController.viewControllers = controllers
            tabBarController.delegate = context.coordinator
            return tabBarController
        }
    
        func updateUIViewController(_ uiViewController: CustomTabBarController, context: Context) {
            uiViewController.selectedIndex = switchTab
        }
    
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    
        class Coordinator: NSObject, UITabBarControllerDelegate {
            let owner: TabBarController
            init(_ owner: TabBarController) {
                self.owner = owner
            }
            func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
                owner.switchTab = tabBarController.selectedIndex
            }
        }
    }
    
    @MainActor
    struct UIKitStyleTabView: View {
        var viewControllers: [UIHostingController<AnyView>]
    
        @Binding var switchTab: Int
    
        struct Tab {
            var view: AnyView
            var barItem: UITabBarItem
    
            init<V: View>(view: V, barItem: UITabBarItem) {
                self.view = AnyView(view)
                self.barItem = barItem
            }
        }
    
        init(_ tabs: [Tab], switchTab: Binding<Int>) {
            self.viewControllers = tabs.map {
                let host = UIHostingController(rootView: $0.view)
                host.tabBarItem = $0.barItem
                return host
            }
            self._switchTab = switchTab
        }
    
        var body: some View {
            TabBarController(controllers: viewControllers, switchTab: $switchTab)
                .edgesIgnoringSafeArea(.all)
        }
    }
    

    Example usage - this shows a 6-tabbed tab view, with a button in the first tab that brings you to the 6th tab.

    struct ContentView : View {
        @State var switchTab: Int = 0
    
        let tabBarSymbolConfig = UIImage.SymbolConfiguration(scale: .medium)
    
        var body : some View {
    
            UIKitStyleTabView([
                UIKitStyleTabView.Tab(view: Button("Go to tab 6") { self.switchTab = 5 }, barItem: UITabBarItem(title: "Social", image: UIImage(systemName: "person.3.fill", withConfiguration: tabBarSymbolConfig)!.withBaselineOffset(fromBottom: 4.5), selectedImage: nil)
                ), UIKitStyleTabView.Tab(view: Text("SearchView"), barItem: UITabBarItem(title: "Search", image: UIImage(systemName: "magnifyingglass", withConfiguration: tabBarSymbolConfig)!.withBaselineOffset(fromBottom: 4.5), selectedImage: nil)
                ), UIKitStyleTabView.Tab(view: Text("WorkoutsView"),barItem: UITabBarItem(title: "Workouts", image: UIImage(systemName: "doc.on.clipboard.fill", withConfiguration: tabBarSymbolConfig)!.withBaselineOffset(fromBottom: 4.5), selectedImage: nil)
                ), UIKitStyleTabView.Tab(view: Text("ExercisesView"), barItem: UITabBarItem(title: "Exercises", image: UIImage(systemName: "sportscourt", withConfiguration: tabBarSymbolConfig)!.withBaselineOffset(fromBottom: 4.5), selectedImage: nil)
                ), UIKitStyleTabView.Tab(view: Text("AccountView"), barItem: UITabBarItem(title: "Me", image: UIImage(systemName: "person.crop.circle", withConfiguration: tabBarSymbolConfig)!.withBaselineOffset(fromBottom: 4.5), selectedImage: nil)
                ), UIKitStyleTabView.Tab(view: Text("SomeOtherView"), barItem: UITabBarItem(title: "Other", image: UIImage(systemName: "person.crop.circle", withConfiguration: tabBarSymbolConfig)!.withBaselineOffset(fromBottom: 4.5), selectedImage: nil)
                )
            ], switchTab: $switchTab)
        }
    }
    

    You can also use SwiftUI Introspect to get a UITabBarController from a TabView directly, but it can only introspect on the versions you specify, so it is not future-proof at all.

    TabView(selection: $tab) {
        ForEach(0..<7) { i in
            Text("Tab \(i)").tabItem { Text("\(i)") }
                .toolbar(.hidden, for: .tabBar)
        }
    }
    .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { tabView in
        tabView.moreNavigationController.isNavigationBarHidden = true
    }