swiftswiftuiuinavigationbarswiftui-navigationviewuinavigationbarappearance

Dynamically change and update color of Navigation Bar in SwiftUI


I'm trying to change the NavigationBar background color in SwiftUI which I have done with the following code. However, I have yet to figure out how I can dynamically change and update the background color of the navigation bar.

struct ContentView: View {
    @State var color: Color = .red
    
    init() {
        let navbarAppearance = UINavigationBarAppearance()
        navbarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.backgroundColor = UIColor(color)
        UINavigationBar.appearance().standardAppearance = navbarAppearance
        UINavigationBar.appearance().compactAppearance = navbarAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navbarAppearance
        
    }
    
    var body: some View {
        NavigationView {
                VStack(spacing: 20) {
                    Button(action: { color = .blue }) {
                        Text("Blue")
                            .font(.title)
                            .bold()
                            .foregroundColor(.white)
                            .frame(width: 100)
                            .padding()
                            .background(Color.blue)
                            .cornerRadius(15)
                    }
                    Button(action: { color = .red }) {
                        Text("Red")
                            .font(.title)
                            .bold()
                            .foregroundColor(.white)
                            .frame(width: 100)
                            .padding()
                            .background(Color.red)
                            .cornerRadius(15)
                    }
                }
                .offset(y: -50)
                .navigationTitle("My Navigation")
        }
    }
    
}

enter image description here This code gives me the correct result, but tapping one of the buttons to change the Color variable does not update the color of the NavigationBar. This is because on initialization the nav bar maintains all of its characteristics, so I need to find a way to change these after, and if possible animate this transition between changing colors. Thanks for any help!


Solution

  • You can use SwiftUI-Introspect, so you are only changing the navigation bar of this NavigationView. It will not affect any other instance.

    You also won't be using .id(...), which is potentially bad because when a view changes identity it can break animations, unnecessarily reinitialize views, break view lifecycles, etc.

    In my example, you save the current instance of the UINavigationController. When the value of color changes, the appearance of the navigation bar is set again.

    Example with Introspect:

    import Introspect
    
    /* ... */
    
    struct ContentView: View {
        @State private var color: Color = .red
        @State private var nav: UINavigationController?
    
        var body: some View {
            NavigationView {
                VStack(spacing: 20) {
                    Button(action: { color = .blue }) {
                        Text("Blue")
                            .font(.title)
                            .bold()
                            .foregroundColor(.white)
                            .frame(width: 100)
                            .padding()
                            .background(Color.blue)
                            .cornerRadius(15)
                    }
    
                    Button(action: { color = .red }) {
                        Text("Red")
                            .font(.title)
                            .bold()
                            .foregroundColor(.white)
                            .frame(width: 100)
                            .padding()
                            .background(Color.red)
                            .cornerRadius(15)
                    }
                }
                .offset(y: -50)
                .navigationTitle("My Navigation")
            }
            .introspectNavigationController { nav in
                self.nav = nav
                updateNavBar()
            }
            .onChange(of: color) { _ in
                updateNavBar()
            }
        }
    
        private func updateNavBar() {
            guard let nav = nav else { return }
            let navbarAppearance = UINavigationBarAppearance()
            navbarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
            navbarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
            navbarAppearance.backgroundColor = UIColor(color)
            nav.navigationBar.standardAppearance = navbarAppearance
            nav.navigationBar.compactAppearance = navbarAppearance
            nav.navigationBar.scrollEdgeAppearance = navbarAppearance
        }
    }
    

    Result:

    Result