iosswiftswiftuibottom-navigation-barxcode16

SwiftUI BottomNavigationBar Interfering with Full-Screen Home View – How to Prevent It?


I'm developing a SwiftUI app with a custom BottomNavigationBar view and a Home view. The Home view is designed to display a background color and image that extend all the way up to the status bar, which works perfectly when accessed on its own. However, when I navigate to Home through the BottomNavigationBar, the background no longer reaches the status bar area.

My goal is to make the BottomNavigationBar affect only the bottom of the screen, without interfering with the status bar or the full-screen display of Home.

BottomNavigationBar.swift

import SwiftUI

struct BottomNavigationBar: View {
    @State private var selectedTab: Int
    @State private var selectedRole: UserRole

    init(initialRole: UserRole = .maker) {
        _selectedRole = State(initialValue: initialRole)
        _selectedTab = State(initialValue: 0)
    }

    var body: some View {
        VStack(spacing: 0) {
            TabView(selection: $selectedTab) {
                roleBasedHomeView(for: selectedTab)
                    .tag(0)

                // Task View
                TaskView()
                    .tag(1)

                // Token View
                TokenView()
                    .tag(2)

                // Dashboard View
                Dashboard()
                    .tag(3)

                // Profile View
                Profile()
                    .tag(4)
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))

            HStack {
                BottomNavItem(icon: "house", title: "Home", isSelected: selectedTab == 0) {
                    selectedTab = 0
                }

                BottomNavItem(icon: "list.bullet", title: "Task", isSelected: selectedTab == 1) {
                    selectedTab = 1
                }

                BottomNavItem(icon: "key.fill", title: "Token", isSelected: selectedTab == 2) {
                    selectedTab = 2
                }

                BottomNavItem(icon: "chart.bar", title: "Dashboard", isSelected: selectedTab == 3) {
                    selectedTab = 3
                }

                BottomNavItem(icon: "person", title: "Profile", isSelected: selectedTab == 4) {
                    selectedTab = 4
                }
            }
            .padding()
            .background(Color.white)
            .shadow(color: Color.gray.opacity(0.2), radius: 5, x: 0, y: -2)
        }
        .edgesIgnoringSafeArea(.bottom) 
                .statusBar(hidden: true)
    }
    
    @ViewBuilder
    private func roleBasedHomeView(for tab: Int) -> some View {
        switch tab {
        case 0:
            switch selectedRole {
            case .maker:
                Home()
            case .approver:
                HomeAR(role: .approver)
            case .releaser:
                HomeAR(role: .releaser)
            case .approverReleaser:
                HomeAR(role: .approverReleaser)
            case .makerApprover:
                HomeAR(role: .makerApprover)
            case .makerApproverReleaserSME:
                HomeAR(role: .makerApproverReleaserSME)
            case .sysAdmin:
                HomeSysAdmin()
            default:
                WelcomeView()
            }
        case 1:
            TaskView()
        case 2:
            TokenView()
        case 3:
            Dashboard()
        case 4:
            Profile()
        default:
            WelcomeView()
        }
    }
}

struct BottomNavItem: View {
    var icon: String
    var title: String
    var isSelected: Bool
    var action: () -> Void
    
    var body: some View {
        Button(action: action) {
            VStack(spacing: 4) {
                Image(systemName: icon)
                    .font(.system(size: 24))
                    .foregroundColor(isSelected ? Color(hex: "#1E8F7D") : .gray)
                
                Text(title)
                    .font(.caption)
                    .foregroundColor(isSelected ? Color(hex: "#1E8F7D") : .gray)
            }
            .frame(maxWidth: .infinity)
        }
    }
}

Home.swift

import SwiftUI

struct Home: View {
    @State private var selectedTab: Int = 0
    @State private var selectedRole: UserRole = .maker
    @State private var showStickyHeader = false
    @State private var isHidden = true
    @State private var selectedCompany: String = "PT Pertamina Tbk"
    @State private var showDropdown = false
    let companies = ["PT Pertamina Tbk", "PLN", "PT Timbul Tenggelam"]
    
    var body: some View {
        ZStack(alignment: .top) {
            Color.primary
                .ignoresSafeArea(edges: .all)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            ScrollView(showsIndicators: false) {
                ZStack(alignment: .top) {
                    VStack(spacing: 0) {
                        Color.primary
                            .frame(height: 250)
                            .overlay(
                                Image("pattern_bg_transparent")
                                    .resizable()
                                    .scaledToFill()
                                    .frame(height: 400)
                                    .opacity(0.8)
                                    .clipped()
                                    .ignoresSafeArea(.all)
                            )
                        
                    }
                    .overlay(
                        GeometryReader { proxy in
                            Color.clear
                                .preference(key: ScrollOffsetPreferenceKey.self, value: proxy.frame(in: .global).minY)
                        }
                    )
                    .onPreferenceChange(ScrollOffsetPreferenceKey.self) { offset in
                        withAnimation(.spring(response: 1.2, dampingFraction: 0.7, blendDuration: 0.8).delay(0.8)) {
                            showStickyHeader = offset < -50
                        }
                    }
                    
                    // Main contents
                    VStack(spacing: 16) {
                        VStack(spacing: 2) {
                            HStack {
                                Text("Fredrick Pardosi")
                                    .font(.system(size: 20, weight: .bold))
                                    .foregroundColor(.white)
                                
                                Spacer()
                                
                                HStack(spacing: 4) {
                                    Image(systemName: "building.2")
                                        .foregroundColor(.white)
                                    
                                    Image(systemName: showDropdown ? "chevron.up" : "chevron.down")
                                        .foregroundColor(.white)
                                }
                                .padding(.horizontal, 8)
                                .padding(.vertical, 6)
                                .background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.2)))
                                .onTapGesture {
                                    withAnimation {
                                        showDropdown.toggle()
                                    }
                                }
                                
                                Image(systemName: "bell")
                                    .foregroundColor(.white)
                                    .padding(8)
                                    .background(Circle().fill(Color.white.opacity(0.2)))
                            }
                            .padding(.horizontal, 16)
                            
                            HStack {
                                Text("Maker")
                                    .font(.system(size: 14, weight: .medium))
                                    .foregroundColor(.white)
                                    .padding(4)
                                
                                Spacer()
                            }
                            .padding(.horizontal, 16)
                        }
                        
                        VStack(spacing: 16) {
                            TotalBalance(isHidden: $isHidden)
                                .padding(.top, 8)
                            
                            DepositLoanView(isHidden: $isHidden)
                            
                            VStack(alignment: .leading, spacing: 16) {
                                PendingTaskView(pending: 52, rejected: 1100, executed: 983, title: "Status Transaksi")
                                
                                ServiceSlider(title: "Status Transaksi by Service")
                                
                                FeaturedMenu()
                            }
                        }
                    }
                }
                .background(Color(UIColor.systemGray6).ignoresSafeArea())
            }
            .refreshable {
                refreshContent()
            }
            
            if showStickyHeader {
                StickyHeader()
                    .transition(.move(edge: .top))
            }
        }
        .navigationBarBackButtonHidden(true)
    }
    
    private func refreshContent() {
        print("Content refreshed!")
    }
}

The Problem

When navigating to Home directly, the background color and image extend perfectly up to the status bar. However, accessing Home through the BottomNavigationBar prevents the background from extending to the status bar.

See the difference between the two images above.

I want the layout to look exactly like it does in Home.swift

How can I ensure that the BottomNavigationBar does not interfere with the full-screen behavior of Home, so that Home can reach up to the status bar?

Any suggestions or guidance on how to properly configure BottomNavigationBar or Home would be greatly appreciated. Thank you!


Solution

  • Because TabView respects the safe area insets by default, child views (like Home) may be restricted inside the safe area. When these limits are already enforced by the parent view (the TabView), using .edgesIgnoringSafeArea(.all) or .ignoresSafeArea() in your Home view is insufficient. We must make sure that the TabView and its child views appropriately disregard the safe area limits in order to resolve this issue. There are two primary approaches that you can attempt.

    TabView(selection: $selectedTab) {
       roleBasedHomeView(for: selectedTab)
           .ignoresSafeArea() // Add this line
           .tag(0)
    
       TaskView()
           .ignoresSafeArea()
           .tag(1)
    
       TokenView()
           .ignoresSafeArea()
           .tag(2)
    
       Dashboard()
           .ignoresSafeArea()
           .tag(3)
    
       Profile()
           .ignoresSafeArea()
           .tag(4)
    }
    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
    

    Then the second one would be to warp the TabView in a ZStack and add the .ignoresSafeArea() after the ZStack closing braces. Let me know if this works

    Something else to consider is changing the ..navigationBarBackButtonHidden(true) to navigationBarHidden(true)