I have a custom TabBar in SwiftUI that can be shown/hidden with animation. The issue is with handling interactions when the TabBar is hidden:
Here's my complete code:
import SwiftUI
struct MainTabView: View {
@State private var selectedTab = 0
@State private var tabBarVisible = true
@State private var tabBarHeight: CGFloat = 0
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .bottom) {
// Main content
TabView(selection: $selectedTab) {
HomeView()
.tag(0)
FavoritesView()
.tag(1)
EmptyView()
.tag(2)
SettingsView()
.tag(3)
}
// Custom tab bar
VStack(spacing: 0) {
Spacer()
if tabBarVisible {
CustomTabBar(selectedTab: $selectedTab, tabbarVisible: $tabBarVisible)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.transition(.move(edge: .bottom))
.background(
GeometryReader { geo in
Color.clear.onAppear {
tabBarHeight = geo.size.height
}
}
)
}
}
}
.ignoresSafeArea(edges: .bottom)
}
.environment(\.tabBarVisibility, $tabBarVisible)
}
}
struct CustomTabBar: View {
@Binding var selectedTab: Int
@Binding var tabbarVisible: Bool
var body: some View {
HStack(spacing: 0) {
TabBarButton(imageName: "house", isSelected: selectedTab == 0, action: { selectedTab = 0 })
TabBarButton(imageName: "magnifyingglass", isSelected: selectedTab == 1, action: { selectedTab = 1 })
TabBarButton(imageName: "bell", isSelected: selectedTab == 2, action: { selectedTab = 2 })
TabBarButton(imageName: "person", isSelected: selectedTab == 3, action: { selectedTab = 3 })
}
.padding(8)
.background(
Color.white
.shadow(color: Color.black.opacity(0.1), radius: 8, x: 0, y: -4)
)
.cornerRadius(25)
.padding(.horizontal)
}
}
struct TabBarButton: View {
let imageName: String
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
Image(systemName: imageName)
.font(.system(size: 24))
.foregroundColor(isSelected ? .white : .black.opacity(0.65))
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.background(isSelected ? Color.purple.opacity(0.7) : Color.clear)
.clipShape(Capsule())
}
}
}
// Environment key for tab bar visibility
struct TabBarVisibilityKey: EnvironmentKey {
static let defaultValue: Binding<Bool> = .constant(true)
}
extension EnvironmentValues {
var tabBarVisibility: Binding<Bool> {
get { self[TabBarVisibilityKey.self] }
set { self[TabBarVisibilityKey.self] = newValue }
}
}
struct ContentView: View {
var body: some View {
MainTabView()
}
}
struct DetailView: View {
@Environment(\.tabBarVisibility) var tabBarVisibility
var body: some View {
ScrollView {
VStack {
// Content here
Button("Toggle TabBar") {
withAnimation(.easeInOut) {
tabBarVisibility.wrappedValue.toggle()
}
}
}
}
.onAppear {
// Hide tab bar when view appears
withAnimation(.easeInOut) {
tabBarVisibility.wrappedValue = false
}
}
.onDisappear {
// Show tab bar when view disappears
withAnimation(.easeInOut) {
tabBarVisibility.wrappedValue = true
}
}
}
}
When TabBar is visible:
When TabBar is hidden:
Can anyone suggest a way to achieve this? I specifically need to prevent interactions with the TabBar area when it's hidden while allowing other UI elements to still be interactive.
if !tabBarVisible {
Rectangle()
.fill(Color.clear)
.ignoresSafeArea()
.contentShape(Rectangle())
.onTapGesture { }
}
Problem: This blocks ALL interactions in that area, including legitimate UI elements.
if !tabBarVisible {
CustomTabBar(selectedTab: $selectedTab, tabbarVisible: $tabBarVisible)
.opacity(0)
.allowsHitTesting(false)
}
Problem: This doesn't block any interactions at all.
if !tabBarVisible {
HStack(spacing: 0) {
// Same buttons as CustomTabBar but with empty actions
}
.opacity(0)
}
I think the reason why you can change tabs even when your custom tab bar is hidden is because the native tab bar is still present. You don't see it because there are no TabItem
associated with your views, but empty buttons with no labels are still present and these are receptive to taps.
To confirm the issue, try adding some TabItem
:
TabView(selection: $selectedTab) {
HomeView()
.tabItem { Text("Home") }
.tag(0)
FavoritesView()
.tabItem { Text("Favourites") }
.tag(1)
// ...
}
You could also try turning on button shapes in the accessibility settings. This will make the tappable areas visible where the empty buttons are present.
The problem can be resolved by hiding the native tab bar. To do this, add .toolbarVisibility(.hidden, for: .tabBar)
to each child view (and remove the TabItem
added above):
TabView(selection: $selectedTab) {
HomeView()
.toolbarVisibility(.hidden, for: .tabBar)
.tag(0)
FavoritesView()
.toolbarVisibility(.hidden, for: .tabBar)
.tag(1)
// ...
}