简体   繁体   中英

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")
        }
    }
    
}

在此处输入图像描述 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!

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:

结果

Yes, appearance is applied to all views created after appearance itself. So we need to reset appearance and then create NavigationView again on color changes.

Here is a demo of possible approach (tested with Xcode 13 / iOS 15)

演示

struct ContentView: View {
    @State var color: Color = .red

    var body: some View {
        MainNavView(barColor: $color)
            .id(color)                // << re-creates !!
    }
}

struct MainNavView: View {
    @Binding var color: Color
    init(barColor: Binding<Color>) {
        self._color = barColor
        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")
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM