swiftswiftui

How to Make SwiftUI Toolbar Background Expand Fully to Ignore Safe Areas?


I'm trying to create a bottom toolbar in SwiftUI where the background color fully expands to the edges of the screen, completely ignoring the safe areas. However, I keep running into issues where there's white space at the bottom or sides of the toolbar.

In my example below, the ignoresSafeArea(edges: .all) doesn't seem to fully expand the black color to the bottom or sides. I've added a picture as a visual example as well:

Picture added as a visual example.

Ideally, I'd like the black background of the toolbar to fully cover the bottom of the screen, ignoring the safe areas completely and dynamically adjust for different devices without introducing white spaces or disproportionate sizing.

How can I achieve this effect in SwiftUI? Any advice or improvements to my implementation would be greatly appreciated!

Example code:

import SwiftUI

// Primary view for app
struct ContentView: View {

    var body: some View {
        NavigationView {
            GeometryReader { geometry in
                let metrics = Metrics(geometry: geometry)
                ZStack{
                    AppColors.background.ignoresSafeArea()
                }

                VStack(spacing: metrics.verticalSpacing * 2) {
                    VStack(spacing: metrics.verticalSpacing * 2) {
                        SpacesView()
                        .frame(height: metrics.tileSize)

                        LettersView()
                            .frame(height: metrics.tileSize)
                    }
                    .padding(.horizontal, metrics.horizontalPadding)
                    .padding(.top, metrics.verticalSpacing * 3)

                    VStack {
                        if gameFinished {
                            GameFinishedView()
                        } else {
                            CurrentScoreView()
                        }
                    }
                    .padding(.horizontal, metrics.horizontalPadding)
                }
                .padding(.vertical, metrics.verticalSpacing * 2)
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    TopBar()
                }
                // Bottom tool bar integrated here
                .toolbar {
                    ToolbarItemGroup(placement: .bottomBar) {
                            BottomBar()
                        }
                }
            }
        }
    }
}

// Bottom tool bar
struct BottomBar: View {

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                // Black toolbar background to fully ignore safe areas
                Color.black
                    .ignoresSafeArea(edges: .all)

                // Toolbar content
                HStack {
                    SubmitButton()
                }
                .padding(.horizontal, 16)
                .padding(.vertical, 10)
            }
            .frame(width: geometry.size.width, height: geometry.size.height)
        }
    }
}

Solution

  • You should use toolbarBackground to set the color of the tool bar.

    .toolbar {
        ToolbarItemGroup(placement: .bottomBar) {
            BottomBar()
        }
    }
    .toolbarBackgroundVisibility(.visible, for: .bottomBar)
    .toolbarBackground(.black, for: .bottomBar)
    

    Before iOS 18, toolbarBackgroundVisibility is (confusingly) also called toolbarBackground, so you'd write .toolbarBackground(.visible, for: .bottomBar).

    This means that you don't need all that GeometryReader, ZStack, Color.black, and other things in BottomBar. BottomBar can just be the three buttons - you don't even need an HStack.

    var body: some View {
        YellowButton()
        Spacer()
        SubmitButton()
        Spacer()
        BlueButton()
    }
    

    If you are targeting versions older than iOS 16, consider using a safeAreaInset instead of toolbar.

    GeometryReader { geo in
        // ...
    }
    .safeAreaInset(edge: .bottom) {
        BottomBar() // in this case BottomBar does need the Color.black and other things that makes it fill the whole width
    }