How to customise NavigationStack background in swiftUI?

By | June 15, 2024

The view background can extend to the corners of the view. This will affect the navigation bar background. This will go behind large and inline navigation bars.

For example, in below example the background colour is applied to the whole screen, but it is covering the navigation bar background too.

struct ContentView: View {
    let gradient = LinearGradient(colors: [Color.orange,Color.green], startPoint: .top, endPoint: .bottom)
    var body: some View {
        NavigationStack {
                ZStack {
                    gradient
                        .opacity(0.25)
                        .ignoresSafeArea()

                    VStack {
                        Text("NavigationStack background can also be seen in this")
                            .padding()
                        Spacer()
                    }
                    .navigationTitle("Screen Background")
                    .font(.title2)
                }
                .navigationBarTitleDisplayMode(.inline)
            }
    }
}

The new initializer introduced with iOS 15 allows background to expand into safe areas. You could manually set safe area edge but by default, it is set to ‘all’ edges.

Adding Solid colours to navigation bar

struct ContentView: View {
    let gradient = LinearGradient(colors: [Color.orange,Color.green], startPoint: .top, endPoint: .bottom)
    var body: some View {
        NavigationStack {
            ZStack {
                Color.green
                    .opacity(0.1)
                    .ignoresSafeArea()

                VStack {
                    Rectangle()
                        .frame(height: 0)
                        .background(Color.green.opacity(0.2))
                    Text("Have the style touching the safe area edge.")
                        .padding()
                    Spacer()
                }
                .navigationTitle("Nav Bar Background")
                .font(.title2)
            }
        }
    }
}

Materials

Instead of rectangle we can use divider. Example is as given below.


struct ContentView: View {
    let gradient = LinearGradient(colors: [Color.orange,Color.green], startPoint: .top, endPoint: .bottom)
    var body: some View {
        NavigationStack {
            ZStack {
                Color.green
                    .opacity(0.1)
                    .ignoresSafeArea()

                VStack {
                    Divider()
                        .background(.ultraThinMaterial)

                    Text("Have the style touching the safe area edge.")
                        .padding()

                    Spacer()
                }
                .navigationTitle("Nav Bar Background")
                .font(.title2)
            }
        }
    }
}

Gradients

Lets use some linear gradient with some angle. So that all colours are visible.

struct ContentView: View {
    var body: some View {
        NavigationStack {
            ZStack {
                Color.green
                    .opacity(0.1)
                    .ignoresSafeArea()

                VStack {
                    Rectangle()
                        .fill(Color.clear)
                        .frame(height: 10)
                        .background(LinearGradient(colors: [.green.opacity(0.3),
                                                            .blue.opacity(0.5)],
                                                   startPoint: .topLeading,
                                                   endPoint: .bottomTrailing)
                        )

                    Text("Have the style touching the safe area edge.")
                        .padding()
                    Spacer()
                }
                .navigationTitle("Nav Bar Background")
                .font(.title2)
            }
        }
    }
}

List and navigation stack

When you scroll up a navigation list, the navigationStack will automatically change into the inline style with a material background.

But this feature will not be available if you customise the navigationstack. In below code if you scroll up the navigation bar will not collapse to inline and the rows do not scroll behind the navigation view. For this to behave normally, we have to customise the NavigationStack.

struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack(spacing: 0) {
                Divider()
                    .background(Color.orange.opacity(0.2))

                ScrollView {
                    ForEach(0 ..< 15) { item in
                        RoundedRectangle(cornerRadius: 12)
                            .fill(Color.orange)
                            .frame(height: 44)
                            .padding()
                    }
                }
            }
            .navigationTitle("List & NavView")
        }
    }
}

Customising navigation bar while scrolling.

In order to retain the navigation bar behaviour we use UINavigationBarAppearance from UIKit as shown below. Now the navigation title shrinks inline retaining it’s default behaviour.

struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack {
                ScrollView {
                    ForEach(0 ..< 15) { item in
                        RoundedRectangle(cornerRadius: 12)
                            .fill(Color.orange)
                            .frame(height: 44)
                            .padding()
                    }
                }
            }
            .navigationTitle("List & NavView")
        }
        .onAppear {
            let appearance = UINavigationBarAppearance()
            appearance.backgroundEffect = UIBlurEffect(style: .systemUltraThinMaterial)
            appearance.backgroundColor = UIColor(Color.orange.opacity(0.2))
            UINavigationBar.appearance().scrollEdgeAppearance = appearance
        }
    }
}

Note: You can set these appearance properties in onAppear, the views init, or even in your App delegate when your app starts. It’s up to you.

Replacing NavigationStack

If you don’t want to go into UIKit land and use UINavigationBarAppearance then you can create your own navigation view.

Using safeAreaInset, you can easily add a view to a scrolling view (List, ScrollView) and have the list scroll behind your custom navigation view. While doing it we have to hide default navigation bar.

struct ContentView: View {
    var body: some View {
        NavigationStack {
            ScrollView {
                ForEach(0 ..< 15) { item in
                    RoundedRectangle(cornerRadius: 12)
                        .fill(Color.orange)
                        .frame(height: 44)
                        .padding()
                }
            }
            .safeAreaInset(edge: .top) {
                VStack(alignment: .leading, spacing: 8) {
                    HStack() {
                        Text("Custom Nav Bar")
                            .font(.largeTitle.weight(.bold))
                        Spacer()
                        Button(action: {}) {
                            Image(systemName: "wifi")
                        }
                    }
                    Text("With safeAreaInset you can create your own custom nav bar.")
                        .font(.caption)
                }
                .padding()
                .background(LinearGradient(colors: [.green.opacity(0.3), .blue.opacity(0.5)],
                                           startPoint: .topLeading, endPoint: .bottomTrailing)
                    .overlay(.ultraThinMaterial)
                )
            }
            .navigationBarHidden(true)
            .tint(.orange)
        }
    }
}

Starting in iOS 16, you can use the toolbarBackground modifier to set a style (color, material, etc).


struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationTitle("Toolbar Background")
                .toolbarBackground(.green.opacity(0.2), for: .navigationBar)
                .toolbarBackground(.visible, for: .navigationBar)
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *