56

I am working with SwiftUI, and I have some issues with the TabBar. I want to hide the TabBar on a specific subview.

Have tried with

UITabBar.appearance().isHidden = true

It only works on the direct views in the TabView. But when I place it in a subview it doesn't work.

Have anyone a solution for this?

Thanks.

Dyngberg
  • 663
  • 1
  • 7
  • 10

22 Answers22

48

iOS 16 native way

.toolbar(.hidden, for: .tabBar)

Daniel
  • 1,473
  • 3
  • 33
  • 63
YichenBman
  • 5,011
  • 8
  • 46
  • 65
  • 4
    YES!!!!! FINALLY :D – TruMan1 Jul 12 '22 at 23:49
  • We live in spectacular days – heyfrank Jul 14 '22 at 15:30
  • 12
    Unfortunately when navigating back it is displaying the tabbar not until the navigation transition has finished. With UIKit the tab bar would be sliding in together with the view – heyfrank Jul 14 '22 at 15:32
  • Hey @heyfrank! Did you find another solution? We need something like `hidesBottomBarWhenPushed`. I don't understand what we don't have this out of the box... – Alexander Khitev Oct 01 '22 at 14:55
  • 1
    Nope, best way is to file a detailed bug report at Apple. The code of this answer should be the way to achieve it, so Apple must fix it. Especially since it's a declarative framework. – heyfrank Oct 03 '22 at 10:26
  • I thought with a `TabView` you have to apply that to the whole `TabView`. But even better: You must / can apply this to each tab item individudally, so you have exact control which tab item shows. – Jan Oct 12 '22 at 14:20
  • @heyfrank I hope they're fixing this issue very soon. It's pretty annoying, because it's a so important feature if you want to build a chat app or something else where the tabbar is hidden. – adri567 Nov 23 '22 at 12:44
  • Is there a way to add animation to this? I have my subview slide in, so if I just attach `.toolbar(.hidden, for: .tabBar)` to the `SubView()`, the tab bar disappears instantly and it looks bad. Is it possible to hide the toolbar using a slide down transition? – Joseph Dec 05 '22 at 10:52
  • If anybody is wondering this answer solves the problem https://stackoverflow.com/a/73601830/5515745. The view is no longer shaking on way back and the tabbar is visible instantly. – Kamil Latosinski Jan 09 '23 at 13:25
  • 2
    This does not really solve the problem because you are using a TabView inside a NavigationView. This is not the way how to use both together, and it leads to more issues. – adri567 Feb 03 '23 at 11:22
  • It's possible to show and hide the tab bar with animation when you make the visibility based on a variable which changes when navigating to another screen `.toolbar(isNavigationStackEmpty ? .visible : .hidden, for: .tabBar)` and you either change this variable with animation or use it as a value for animation modifier. This trick works for me in some places but doesn't work in others, I hope together we'll figure out how to set this up right – zh. Feb 21 '23 at 06:27
18

iOS 14

Install the Introspect SwiftPM: https://github.com/siteline/SwiftUI-Introspect

struct SomeView: View{
    
    @State var uiTabarController: UITabBarController?
    
    var body: some View {
        List {
            -your code here-
        }
        
        .navigationBarTitle("Title", displayMode: .inline)
        .introspectTabBarController { (UITabBarController) in
            UITabBarController.tabBar.isHidden = true
            uiTabarController = UITabBarController
        }.onDisappear{
            uiTabarController?.tabBar.isHidden = false
        }
    }
}

In this code in uiTabarController, we are taking the reference of UITabarController. When we go back then we enabled the Tabar again. So, that's why this is needed.

Muhammad Abbas
  • 488
  • 5
  • 12
10

iOS 15 solution

This solution works well except with view modifier in the SwiftUI.TabView. Since my TabView is in the struct that conforms App, it looks like there still is not any UITabBar subview in the connected scenes.

With the code below, you only need to use showTabBar() or hiddenTabBar() in your SwiftUI.View.

extension UIApplication {
    var key: UIWindow? {
        self.connectedScenes
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?
            .windows
            .filter({$0.isKeyWindow})
            .first
    }
}


extension UIView {
    func allSubviews() -> [UIView] {
        var subs = self.subviews
        for subview in self.subviews {
            let rec = subview.allSubviews()
            subs.append(contentsOf: rec)
        }
        return subs
    }
}
    

struct TabBarModifier {
    static func showTabBar() {
        UIApplication.shared.key?.allSubviews().forEach({ subView in
            if let view = subView as? UITabBar {
                view.isHidden = false
            }
        })
    }
    
    static func hideTabBar() {
        UIApplication.shared.key?.allSubviews().forEach({ subView in
            if let view = subView as? UITabBar {
                view.isHidden = true
            }
        })
    }
}

struct ShowTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            TabBarModifier.showTabBar()
        }
    }
}
struct HiddenTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            TabBarModifier.hideTabBar()
        }
    }
}

extension View {
    
    func showTabBar() -> some View {
        return self.modifier(ShowTabBar())
    }

    func hiddenTabBar() -> some View {
        return self.modifier(HiddenTabBar())
    }
}
Gabriel Sória
  • 336
  • 3
  • 9
  • While this seems to work initially, if the user pulls and drags on the edge of the to navigate back and then let goes, the tab bar appears to appear. – ATK Sep 27 '21 at 17:29
  • Hi @Gabriel, can't understand how to resize view after it will showing? tabbar hides, but his size from bottom still affecting other views... Problem resolves if I move app to inactive state using swipe and come back.. – Sergey Udalov Apr 19 '23 at 04:59
9

here's no way to hide TabView so I had to add TabView inside ZStack as this:

var body: some View {
    ZStack {
        TabView {
            TabBar1().environmentObject(self.userData)
                .tabItem {
                    Image(systemName: "1.square.fill")
                    Text("First")
            }
            TabBar2()
                .tabItem {
                    Image(systemName: "2.square.fill")
                    Text("Second")
            }
        }

        if self.userData.showFullScreen {
            FullScreen().environmentObject(self.userData)

        }
    }
}

UserData:

  final class UserData: ObservableObject {
    @Published var showFullScreen = false
}

TabBar1:

struct TabBar1: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        Text("TabBar 1")
            .edgesIgnoringSafeArea(.all)
            .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            .background(Color.green)
            .onTapGesture {
                self.userData.showFullScreen.toggle()
        }
    }
}

FullScreen:

struct FullScreen: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        Text("FullScreen")
            .edgesIgnoringSafeArea(.all)
            .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            .background(Color.red)
            .onTapGesture {
                self.userData.showFullScreen.toggle()
        }
    }
}

check full code on Github

there's also some other ways but it depends on the structure of the views

fakiho
  • 521
  • 4
  • 13
9

It's working, only changes are need to be called on the main queue

struct ShowTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            DispatchQueue.main.async {
                Tool.showTabBar()
            }
        }
    }
}

struct HiddenTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            DispatchQueue.main.async {
                Tool.hiddenTabBar()
            }
        }
    }
}

Traverse the allsubview of the window to hide the UITabBar. You can write it as ViewModifier and use it in SwiftUI or use tools to hide it. This method works for me.

    extension UIView {
        
        func allSubviews() -> [UIView] {
            var res = self.subviews
            for subview in self.subviews {
                let riz = subview.allSubviews()
                res.append(contentsOf: riz)
            }
            return res
        }
    }
    
    struct Tool {
        static func showTabBar() {
            UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
                if let view = v as? UITabBar {
                    view.isHidden = false
                }
            })
        }
        
        static func hiddenTabBar() {
            UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
                if let view = v as? UITabBar {
                    view.isHidden = true
                }
            })
        }
    }
    
    struct ShowTabBar: ViewModifier {
        func body(content: Content) -> some View {
            return content.padding(.zero).onAppear {
                Tool.showTabBar()
            }
        }
    }
    struct HiddenTabBar: ViewModifier {
        func body(content: Content) -> some View {
            return content.padding(.zero).onAppear {
                Tool.hiddenTabBar()
            }
        }
    }
    
    extension View {
        func showTabBar() -> some View {
            return self.modifier(ShowTabBar())
        }
        func hiddenTabBar() -> some View {
            return self.modifier(HiddenTabBar())
        }
    }
zdravko zdravkin
  • 2,090
  • 19
  • 21
Tema Tian
  • 331
  • 3
  • 12
  • 1
    It's working fine me also but I'm facing some issues above iOS 14.0 like tabbar is reappearing when user focusing the textfield even I hided the tabbar. Is there any idea why it's occuring? – Ram Chandran Feb 10 '21 at 07:00
9

iOS 14 Simple Solution

Install the Introspect SwiftPM: https://github.com/siteline/SwiftUI-Introspect

var body: some View {
    List {
        -your code here-
    }
    
    .navigationBarTitle("Title", displayMode: .inline)
    .introspectTabBarController { (UITabBarController) in
        UITabBarController.tabBar.isHidden = true
    }
}

NOTE: You have to re-enable the TabBar it in the parent view or it will still be hidden.

.introspectTabBarController { (UITabBarController) in
            UITabBarController.tabBar.isHidden = false
}
Sarvesh Sridhar
  • 387
  • 3
  • 3
  • It hides successfully, but I cannot get it to re-enable it when the user navigates back to the parent. I think this is because when the user goes backwards in the navigation the parent view does not get refreshed. How did you solve this issue? – Gary Oct 29 '20 at 01:19
  • Works flawless, but do not forget to make your view fullscreen, otherwise you will have a big gap at the bottom of your view – niko Nov 09 '20 at 20:58
  • 1
    @Gary Declare a variable to save the tabBar reference and then .onDisappear() of the same view set it back. ``` @State private var tabBar: UITabBar? .introspectTabBarController { UITabBarController in tabBar = UITabBarController.tabBar self.tabBar?.isHidden = true } .onDisappear() { self.tabBar?.isHidden = false } ``` – xdeleon Nov 23 '20 at 01:43
  • Hi, I have the same problem as Gary. The Tabbar doesn't appear again. Does somebody have a solution because @xdeleons doesn't work – Lukas Dec 03 '20 at 19:16
  • Actually not working perfectly: you have to define `isHidden = false` everywhere else; – LiangWang May 29 '21 at 21:52
  • @LiangWang have you found a better solution? – A Akrm Sep 29 '21 at 14:07
8

enter image description here

To hide TabBar when we jumps towards next screen we just have to place NavigationView to the right place. Makesure Embed TabView inside NavigationView so creating unique Navigationview for both tabs.

As explained here https://janeshswift.com/ios/swiftui/how-to-hide-tabbar-on-push-with-swiftui/

import SwiftUI

struct TabBarView: View {
    
    @State var tabSelection: Int = 0
    @State var tabArray = ["Profile", "Settings"]
    
    var body: some View {
        NavigationView {
            TabView(selection: $tabSelection){
                ForEach(0 ..< tabArray.count, id: \.self) { indexValue in
                    NavigationLink(destination: DetailView()){
                        VStack{
                            Text("\(tabArray[indexValue]) tab -- Click to jump next view")
                        }
                    }
                    .tabItem {
                        Image(systemName: "\(indexValue).circle.fill")
                        Text(tabArray[indexValue])
                    }
                    .tag(indexValue)
                    
                }
            }
            .navigationBarTitle(tabArray[tabSelection])
        }
    }
}
struct DetailView: View {
    var body: some View {
        Text("Detail View")
            .navigationBarTitle("NavigatedView")
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle("helllo")
    }
}
Jack
  • 13,571
  • 6
  • 76
  • 98
  • Could you please explain what does "so creating unique NavigationView for both tabs" mean? I'm quite new to SwiftUI and this luckly working solution is very confusing. Why when I wrap my TabView in NavigationView (or in my case NavigationStack) suddenly it disappears when redirecting to a SubView and previously it was always visible? – Kamil Latosinski Jan 09 '23 at 13:39
5

The basic idea I'm using is to combine ObservableObject and ZStack. I have placed TabView into ZStack with conditional subview presentation. It's look like. Look through github repo

Dmitry
  • 141
  • 1
  • 8
  • 1
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – mufazmi May 18 '21 at 19:48
4

iOS 16

Usage of .toolbar modifier. Create state property of type Visibility and manage its value from the pushed view

First view

struct FirstTab: View {
    @State var tabBarVisibility: Visibility = .visible

    var body: some View {
        NavigationView {
            NavigationLink(destination: WidgetDetailView(tab: self)) {
                Text("test")
            }   
        }
        .toolbar(tabBarVisibility, for: .tabBar)
    }
}

Second view

struct WidgetDetailView: View {
    var tab: FirstTab
    
    var body: some View {
        Rectangle()
            .foregroundColor(Color.red)
            .onAppear {
                tab.tabBarVisibility = .hidden
            }
            .onDisappear {
                tab.tabBarVisibility = .visible
            }
    }
}
SwiftStudier
  • 2,272
  • 5
  • 21
  • 43
3

On iOS 16

Use .toolbar(.hidden, for: .tabBar).

For example:

var body: some View {
    TabView {
        FirstView()
            .tabItem {
                Text("First tab")
            }
            .toolbar(.hidden, for: .tabBar)

        SecondView()
            .tabItem {
                Text("Second tab")
            }
            .toolbar(.hidden, for: .tabBar)
    }
}

Note that .toolbar(.hidden, for: .tabBar) is applied to each tabItem, not to the parent view. YMMV if you have a different structure (like a parent NavigationView etc.)

spnkr
  • 952
  • 9
  • 18
2

in general, it's nice to be able to create pages with an w/o tabbar it looks smooth and your page content doesn't change it's size while hiding tabbar on the page

solution is

  • hide tabbar from the root container
  • add custom tabbar modifier
  • use this modifier on navViews to show the tabbar for all nav view hierarchy OR use it on the specific pages in the view hierarchy

here is a small sample project how your app could look like with this approach https://github.com/alexis-ag/swiftui_classic-tabview_show-hide

  • This appears to me to be the best approach to the problem. Tested on iOS 14 and 15. – ATK Sep 27 '21 at 17:30
  • Dude you are awesome. this is best solution. motivation part of your solution is where I was struggling as I was thinking that at least this should work. SwiftUI sucks. – Kraming Mar 11 '23 at 06:13
  • note: in ios 16 you are able to control tabbar appearance with specific modifiers look at this please: https://sarunw.com/posts/swiftui-tabview-color/ – Alexey Grigorjev Mar 31 '23 at 10:00
2

Install the Introspect SwiftPM: https://github.com/siteline/SwiftUI-Introspect

in order to use this you need to create a variable of type UITabBar in the view you want the tabbar to be hidden...

enter code here

@State private var tabBar: UITabBar?

then below the navigationView in the same view you have to add this line:

            .introspectTabBarController { UITabBarController in tabBar = UITabBarController.tabBar
                self.tabBar?.isHidden = true } .onDisappear() { self.tabBar?.isHidden = false }
mamin
  • 19
  • 1
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 17 '21 at 08:05
1

It is actually possible to get the underlying UITabbarController for TabView by using this handy little framework :

https://github.com/siteline/SwiftUI-Introspect

This solution uses the MVVM pattern as an example to have programmatic control over the Tabbar visibility, and be able to show, hide, enable, disable form anywhere in the code using NSNotifications

SwiftUI View : Setup the tabview like this

struct MainTabView: View {

var viewModel: MainTabViewModel

var body: some View {

    TabView() {

        Text("View1")
        .tabItem {
            Text("View1")
        }

        Text("View2")
        .tabItem {
            Text("View2")
        }

    }

    .introspectTabBarController { tabBarController in
        // customize here the UITabBarViewController if you like
        self.viewModel.tabBarController = tabBarController
    }

}
}

Then for the ViewModel

final class MainTabViewModel: ObservableObject {

var tabBarController: UITabBarController?

init() {
    startListeningNotifications()
}

func startListeningNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(showTabbarView), name: "showBottomTabbar", object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(hideTabbarView), name: "hideBottomTabbar", object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(enableTabbarTouch), name: "enableTouchTabbar", object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(disableTabbarTouch), name: "disableTouchTabbar", object: nil)
}

@objc func showTabbarView() {
    self.tabBarController?.tabBar.isHidden = false
}

@objc func hideTabbarView() {
    self.tabBarController?.tabBar.isHidden = true
}

@objc func disableTabbarTouch() {
    self.tabBarController?.tabBar.isUserInteractionEnabled = false
}

@objc func enableTabbarTouch() {
    self.tabBarController?.tabBar.isUserInteractionEnabled = true
}

deinit {
    NotificationCenter.default.removeObserver(self)
}


}

and finally to control the tabbar, just use these fonctions from wherever you feel like (will be in the viewmodels in the pattern of this example)

public func showTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .showBottomTabbar, object: nil)
    }
}

public func hideTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .hideBottomTabbar, object: nil)
    }
}

public func enableTouchTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .enableTouchTabbar, object: nil)
    }
}

public func disableTouchTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .disableTouchTabbar, object: nil)
    }
}
Tao-Nhan Nguyen
  • 5,508
  • 2
  • 13
  • 9
1

Increase the frame size of TabView like this:

.frame(width: UIScreen.main.bounds.width, height: showTabbar ? UIScreen.main.bounds.height : UIScreen.main.bounds.height + 100.00)
Syscall
  • 19,327
  • 10
  • 37
  • 52
1

Not ideal and hacky but the simplest thing to do for me that is working very well was to hide the navigationBar of the outer navigationView and then add another navigationView in each of the TabView's views. Works well so far:

struct LaunchView: View {
    var body: some View {
        NavigationView {
            TabView {
                ViewA()
                    .tabItem {
                        Label("TabA", systemImage: "some.image")
                    }
                ViewB()
                    .tabItem {
                        Label("TabB", systemImage: "some.image")
                    }
                ViewC()
                    .tabItem {
                        Label("TabC", systemImage: "some.image")
                    }
            }
            .navigationBarHidden(true)
        }
    }
}

struct ViewA: View {
        
    var body: some View {
        NavigationView {
            // Content
            .navigationTitle("Settings")
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

This way you can set the title but also the .toolBarItem's in each separate view.

alionthego
  • 8,508
  • 9
  • 52
  • 125
  • I found this the other day and I've been really happy with it. However, up till today I'd only been using iOS 14 simulators. If you use iOS 15, you end up with a bigger space at the top of child views, aka two NavigationViews. You can fix this by removing your child view NavigationViews but you then get the tab bar in child views I haven't been able to find a workaround so far. – Jules Jan 18 '22 at 21:59
1

I have tried to use https://stackoverflow.com/a/62963499/11844048 solution but the TabBar hide in all views once I landed this view. I have modified it a bit to achieve to hide TabBar in single view.

   struct AppInfoView: View {
        @Environment(\.presentationMode) var mode: Binding<PresentationMode>
        var body: some View {
            ZStack{
            }
            .frame(maxWidth: .infinity)
            .background(Color("homepage_bg")).ignoresSafeArea(.all)
            .onAppear{
                UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
                    if let view = v as? UITabBar {
                        view.isHidden = true
                    }
                })
            }
            .onDisAppear(...) //it works too. But seeing TabBar shown bit delay when naviagting back. So below the customizable back button.
            .navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: Button(action : {
                self.mode.wrappedValue.dismiss()
                UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
                    if let view = v as? UITabBar {
                        view.isHidden = false
                    }
                })
            }){
                Image(systemName: "chevron.left")
            })
}

extension UIView {

        func allSubviews() -> [UIView] {
            var res = self.subviews
            for subview in self.subviews {
                let riz = subview.allSubviews()
                res.append(contentsOf: riz)
            }
            return res
        }
    }
Alwin Jose
  • 696
  • 1
  • 5
  • 13
1

Most answers here deal with this requirement in one of two ways:

  • import a framework to locate the UITabBarController
  • modify the view hierarchy (ZStack, NavigationView, ...)

The first one is a clean approach: it locates the underlying element that enables the desired action. However, it may be overkill for a single use case.

The second approach involves some tradeoffs and could be generally considered a smell, since it introduces hierarchy changes for the sake of working around the lack of access to the required element.

Instead, we could follow a clean, simple approach by creating a protocol extension like so:

import UIKit

protocol TabBarAppearanceDelegate {
    func toggleVisibility()
    func hideTabBar()
    func showTabBar()
    // add more methods to control appearance as needed
}

extension TabBarAppearanceDelegate {
    private var tabBarController: UITabBarController? {
        // this is where we access the underlying element, no need to import a framework for a one-liner
        UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController?.children.first as? UITabBarController
    }
    
    func toggleVisibility() {
        tabBarController?.tabBar.isHidden.toggle()
    }
    
    func hideTabBar() {
        tabBarController?.tabBar.isHidden = true
    }
    
    func showTabBar() {
        tabBarController?.tabBar.isHidden = false
    }
}

Then we can make any object conform to this protocol, and inject it as dependency in the views as needed. This will depend on your architecture but it could go like follows.

This is where you'd keep app-wide state, an ObservableObject (you could designate a different one, if preferred):

import Foundation

class StateController: ObservableObject {
    // you would typically manage app-wide state here
}

// this is where we adopt the needed behaviour
extension StateController: TabBarAppearanceDelegate {}

We can now inject the object as a view dependency:

@main
struct TabBarVisibilityApp: App {
    private let stateController = StateController()
    
    var body: some Scene {
        WindowGroup {
            TabView {
                NavigationView {
                    SampleView(tabBarAppearanceDelegate: stateController)
                }
                .tabItem {
                    Label("Home", systemImage: "house")
                }
            }
        }
    }
}

This is how you would use it (valid for in any view that requires the behaviour):

import SwiftUI

struct SampleView: View {
    let tabBarAppearanceDelegate: TabBarAppearanceDelegate
    
    var body: some View {
        VStack {
            Spacer()
            Button(action: {
                tabBarAppearanceDelegate.toggleVisibility()
            } ) {
                Text("Toggle tab bar visibility")
            }
            Spacer()
        }
    }
}

This approach is simple, testable, and requires no extra dependencies... until Apple provides a direct way to control tab bar visibility with a SwiftUI API.

atineoSE
  • 3,597
  • 4
  • 27
  • 31
  • Great approach, but it just blacks out the tabBar. Any idea how SwiftUI will expand to use the space? – Nicolas Degen Nov 30 '21 at 08:36
  • Um, I don't quite follow @NicolasDegen. Will it be something related to `scrollEdgeAppearance`? There is some new behaviour in iOS15 that may affect the tab bar. See here for instance: https://stackoverflow.com/a/69467581/1811775 – atineoSE Dec 16 '21 at 15:46
  • This does work for me, it's almost perfect, I used a `@StateObject` in my app file and `@EnvironmentObject` where you have sample view. I also used onAppear and onDisappear, for show / hide. However onDisappear was quite slow to show the tab bar again :( – Jules Jan 12 '22 at 19:39
0

It is possible! Basically your task is to extract UITabBar somehow and then hide it programatically.

Below is the code which emulates tab bar hiding on push behaviour.

struct ContentView: View {
    var body: some View {
        TabView {
            ForEach(titles, id: \.self) { title in
                NavigationView {
                    view(fromTitle: title)
                }
                .tabItem {
                    Text(title)
                    Image(systemName: "photo")
                }
                .tag(title)
            }
        }
    }

    private let titles = ["one", "two"]

    @ViewBuilder
    private func view(fromTitle title: String) -> some View {
        if title == "one" {
            RegularView(title: title)
                .navigationTitle(title)
                .navigationBarTitleDisplayMode(.inline)
        } else {
            HideOnPushView(title: title)
                .navigationTitle(title)
                .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct RegularView: View {
    let title: String

    var body: some View {
        VStack(spacing: 20) {
            Text(title)
            NavigationLink("Regular push") {
                Text("1111111")
            }
        }
    }
}

struct HideOnPushView: View {
    let title: String

    var body: some View {
        VStack(spacing: 20) {
            Text(title)
            NavigationLink("Hide on push") {
                Text("222222")
                    .onAppear {
                        tabBar?.hide()
                    }
            }
        }
            .background(
                TabBarExtractor(tabBar: $tabBar)
            )
            .onAppear {
                tabBar?.show()
            }
    }

    @State private var tabBar: UITabBar?
}

TabBar extractor code:

import SwiftUI

struct TabBarExtractor: UIViewControllerRepresentable {
    @Binding var tabBar: UITabBar?

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}

    func makeUIViewController(context: Context) -> some UIViewController {
        let controller = ViewController()
        controller.onTabBarAppearance = {
            tabBar = $0
        }
        return controller
    }
}

private extension TabBarExtractor {
    class ViewController: UIViewController {
        var onTabBarAppearance: ((UITabBar) -> Void)?

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)

            if let tabBar = self.tabBarController?.tabBar {
                onTabBarAppearance?(tabBar)
            } else {
                print("Could not locate TabBar! Try change extractor place in views hierarchy.")
            }
        }
    }
}

TabBar category:

import UIKit

extension UITabBar {
    func toggleVisibility() {
        if isHidden {
            show()
        } else {
            hide()
        }
    }

    func show() {
        guard isHidden else { return }

        let visibleY = frame.origin.y
        let hiddenY = visibleY + frame.height
        frame.origin.y = hiddenY
        isHidden = false

        UIView.animate(withDuration: 0.3) { [weak self] in
            self?.frame.origin.y = visibleY
        }
    }

    func hide() {
        guard !isHidden else { return }

        let visibleY = frame.origin.y
        let hiddenY = visibleY + frame.height

        UIView.animate(withDuration: 0.3) { [weak self] in
            self?.frame.origin.y = hiddenY
        } completion: { [weak self] completed in
            guard completed else { return }

            self?.isHidden = true
            self?.frame.origin.y = visibleY
        }
    }
}
alex1704
  • 479
  • 5
  • 8
0

Browsed through all the answers, there are always deficiencies

I chose custom, which has the following benefits:

1.Support iOS15

2.TabBarView does not nest NavigationView

3.Do not break the logic of TabBarView

4.Requires only a small amount of code and is fully customizable

copy my code to run

core code


import SwiftUI

@main
struct HideTabBarViewApp: App {
    
    init() {
        UITabBar.appearance().isHidden = true
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}


struct ContentView: View {
    
    @State var selection = 0
    
    var body: some View {
        TabView(selection: $selection) {
            TabItemView(selection: $selection, tabBarType: .home) {
                PageView(pageType: .home)
            }
            .tag(0)
            
            TabItemView(selection: $selection, tabBarType: .contact) {
                PageView(pageType: .contact)
            }
            .tag(1)
            
            TabItemView(selection: $selection, tabBarType: .search) {
                PageView(pageType: .search)
            }
            .tag(2)
            
            TabItemView(selection: $selection, tabBarType: .user) {
                PageView(pageType: .user)
            }
            .tag(3)
        }
    }
}



Custom TabItemView



struct TabItemView<Content: View>: View {
    
    @Binding var selection: Int
    @State var tabBarType: TabBarType
    @ViewBuilder let content: () -> Content
    
    var imageArray = ["rectangle.roundedtop", "message", "magnifyingglass", "person"]
    
    var body: some View {
        NavigationView {
            VStack {
                content()
            }
            .frame(maxWidth:.infinity, maxHeight: .infinity)
            .overlay(alignment: .bottom) {
                tabBarView
            }
        }
    }
    
    var tabBarView: some View {
        HStack {
            ForEach(Array(imageArray.enumerated()), id: \.element) { index, image in
                Button {
                    withAnimation {
                        selection = index
                    }
                } label: {
                    Image(systemName: image)
                        .font(.title2)
                        .foregroundColor(index == tabBarType.rawValue ? .blue  : .gray)
                }
                .frame(maxWidth: .infinity)
            }
        }
        .frame(height: 40, alignment: .top)
        .padding(.horizontal)
        .padding(.top)
        .background(.bar)
    }
}


some business logic code



struct PageView: View {

    @State var pageType: PageType
    
    var body: some View {
        VStack {
            List {
                ForEach((1...20), id: \.self) {
                    NavigationLink {
                        SettingView()
                    } label: {
                        Text("Go to setting")
                    }
                    .id($0)
                }
            }
        }
        .navigationTitle(pageType.rawValue)
        .navigationBarTitleDisplayMode(.inline)
        .frame(maxWidth:.infinity, maxHeight: .infinity)
    }
}

struct SettingView: View {
    
    var body: some View {
        Text("Setting Page")
            .font(.title)
            .navigationTitle("Setting")
            .navigationBarTitleDisplayMode(.inline)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enum PageType: String {
    case home = "Home"
    case contact = "Contact"
    case search = "Search"
    case user = "User"
}

enum TabBarType: Int {
    case home = 0
    case contact = 1
    case search = 2
    case user = 3
}




gaohomway
  • 2,132
  • 1
  • 20
  • 37
0

I made a Modifier utility that hides the TabBar for iOS 16 or older versions [I successfully tested it on iOS 16 and iOS 15].

Create TabBarModifier swift file and add the following:

import SwiftUI

/// adapted from [TabBarModifier](https://github.com/artemisak/DiaCompanion_iOS/blob/360258e07d4a3e3521a1ec6893f29f175d67034f/dia/Extensions/extensions.swift#L86-L87)
extension View {
    func showTabBar() -> some View {
        if #available(iOS 16.0, *) {
            return toolbar(.visible, for: .tabBar)
        } else {
            return modifier(ShowTabBar())
        }
    }
    
    func hideTabBar() -> some View {
        if #available(iOS 16.0, *) {
            return toolbar(.hidden, for: .tabBar)
        } else {
            return modifier(HideTabBar())
        }
    }
}

private struct HideTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            TabBarModifier.hideTabBar()
        }
    }
}

private struct ShowTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            TabBarModifier.showTabBar()
        }
    }
}

private struct TabBarModifier {
    static func showTabBar() {
        guard let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
            return
        }
        
        keyWindow.allSubviews().forEach { subView in
            if let tabBar = subView as? UITabBar {
                tabBar.isHidden = false
            }
        }
    }
    
    static func hideTabBar() {
        guard let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
            return
        }
        
        keyWindow.allSubviews().forEach { subView in
            if let tabBar = subView as? UITabBar {
                tabBar.isHidden = true
            }
        }
    }
}

private extension UIView {
    func allSubviews() -> [UIView] {
        var subviews = [UIView]()
        subviews.append(contentsOf: self.subviews)
        subviews.forEach { subview in
            subviews.append(contentsOf: subview.allSubviews())
        }
        return subviews
    }
}

Then use it like any modifier. example:

struct Example: View {
    var body: some View {
        StatsView()
            .hideTabBar()
    }
}

let me know if there is a way to cleanup or improve this :)

leo
  • 113
  • 3
  • 11
-1

just use UIKit‘s UINavigationController, like this:

let host = UINavigationController(rootViewController:
UIHostingController(rootView: HLHome()))
Syscall
  • 19,327
  • 10
  • 37
  • 52
-2

I had the same problem, and ended up using:

.opacity(hideTabBar == true ? 0 : 1)

where hideTabBar is a Bool that I pass around to the views that need to hide the TabBar().

So basically you should try something like this:

 TabView(selection: your desired tab).opacity(hideTabBar == true ? 0 : 1)
cseh_17
  • 194
  • 2
  • 10