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.