skip to Main Content

There have been several questions like this one. There is a small difference which I didn’t notice in any other answers. Basically I have a TabView and EACH of its items is wrapped inside NavigationView. Because it is done this way and not the other way around first NavigationView and than TabView the way to hide the view is not that simple.

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

extension UITabBar {
    private static var originalY: Double?
    
    static public func changeTabBarState(shouldHide: Bool) {
        let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
        windowScene?.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ view in
            if let tabBar = view as? UITabBar {
                if !tabBar.isHidden && shouldHide {
                    originalY = tabBar.frame.origin.y
                    tabBar.frame.origin.y = UIScreen.main.bounds.height + 200
                } else if tabBar.isHidden && !shouldHide {
                    guard let originalY else {
                        return
                    }
                    tabBar.frame.origin.y = originalY
                }
                tabBar.isHidden = shouldHide
            }
        })
    }
}

struct MyTabView: View {
    @Environment(.colorScheme) var colorScheme
    @State var toggle = false
    
    var body: some View {
        TabView {
            NavigationView {
                ContentView(toggle: $toggle)
            }
            .tabItem {
                Text("Profile")
                    .foregroundColor(colorScheme == .dark ? .white : .black)
            }
        }
        .accentColor(colorScheme == .dark ? .white : .black)
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct ContentView: View {
    @Binding var toggle: Bool
    
    var body: some View {
        NavigationLink(isActive: $toggle, destination: {
            Button("dismiss") {
                UITabBar.changeTabBarState(shouldHide: false)
                toggle.toggle()
            }
            .navigationBarBackButtonHidden()
            .navigationBarHidden(true)
            .onAppear {
                UITabBar.changeTabBarState(shouldHide: true)
            }
        }, label: {
            Text("Click")
        })
    }
}

@main
struct AppTestingOne: App {
    var body: some Scene {
        WindowGroup {
            MyTabView()
        }
    }
}

The problem I get when doing this is the following. When I click the button at first screen I get sent here:

Before Rotate

On first look there doesn’t seem to be a problem. In fact the button dismiss has like an invisible padding from the bottom. The interesting part is that when I rotate to landscape back to fullscreen, this padding disappears.

After rotate

When you look at these pictures there doesn’t seem to be a problem, but when you have something at the top and at the bottom it gets pushed up and fixed when you re-rotate it.

2

Answers


  1. Chosen as BEST ANSWER

    I found out a very cool solution. When I hide the tabBar I can push its superview down depending on the phone (formula needs to be calculated) and after rotation it continues to work just fine by ignoring by how much I have pushed it and going back to the way it should be which because I calculated it for iPhone 12,13,14 and it works just as fine.

    extension UIView {
        func allSubviews() -> [UIView] {
            var allSubviews = subviews
            for subview in subviews {
                allSubviews.append(contentsOf: subview.allSubviews())
            }
            return allSubviews
        }
    }
    
    extension UITabBar {
        private static var originalY: Double?
        
        static public func changeTabBarState(shouldHide: Bool) {
            let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
            windowScene?.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ view in
                if let tabBar = view as? UITabBar {
                    if !tabBar.isHidden && shouldHide {
                        originalY = (tabBar.superview?.frame.origin.y)!
                        tabBar.superview?.frame.origin.y = (tabBar.superview?.frame.origin.y)! + 4.5
                    } else if tabBar.isHidden && !shouldHide {
                        guard let originalY else {
                            return
                        }
                        tabBar.superview?.frame.origin.y = originalY
                    }
                    tabBar.isHidden = shouldHide
                    tabBar.superview?.setNeedsLayout()
                    tabBar.superview?.layoutIfNeeded()
                }
            })
        }
    }
    
    struct MyTabView: View {
        @Environment(.colorScheme) var colorScheme
        @State var toggle = false
        
        var body: some View {
            TabView {
                NavigationView {
                    RandomView(toggle: $toggle)
                }
                .tabItem {
                    Text("Profile")
                        .foregroundColor(colorScheme == .dark ? .white : .black)
                }
            }
            .accentColor(colorScheme == .dark ? .white : .black)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
    
    struct RandomView: View {
        @Binding var toggle: Bool
        
        var body: some View {
            NavigationLink(isActive: $toggle, destination: {
                Button("dismiss") {
                    UITabBar.changeTabBarState(shouldHide: false)
                    toggle.toggle()
                }
                .navigationBarBackButtonHidden()
                .navigationBarHidden(true)
                .onAppear {
                    UITabBar.changeTabBarState(shouldHide: true)
                }
            }, label: {
                Text("Click")
            })
        }
    }
    
    @main
    struct AppTestingTwo: App {
        var body: some Scene {
            WindowGroup {
                MyTabView()
            }
        }
    }
    

  2. If you’re building for iOS 16, you can simply use

    .toolbar(.hidden, for: .tabBar)
    

    Example:

    struct ContentView: View {
        
        var body: some View {
            TabView() {
                FirstScreen()
                
                NavigationStack {
                    Text("second")
                        .navigationTitle("second")
                }
                .tabItem {
                    Label("second", systemImage: "2.circle")
                }
                NavigationStack {
                    Text("third")
                        .navigationTitle("third")
                }
                .tabItem {
                    Label("third", systemImage: "3.circle")
                }
            }
        }
    }
    
    struct FirstScreen: View {
        
        @State private var hideTabBar = false
        
        var body: some View {
            NavigationStack() {
                VStack {
                    Button("Toggle tab bar") {
                        withAnimation {
                            hideTabBar.toggle()
                        }
                    }
                    .padding()
                    Spacer()
                }
                .navigationTitle("first")
            }
            .toolbar(hideTabBar ? .hidden : .visible, for: .tabBar)
            .tabItem {
                Label("first", systemImage: "1.circle")
            }
        }
    }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search