SwiftUI - Dynamically refresh UINavigationBar.appearance().backgroundColor

Viewed 753

I am trying to implement a colour theme selector in my app. The last hurdle I am trying to overcome is dynamically changing the colour of my UINavigationBar. At present, my function to change the colour does work, but it only takes effect when the app has been relaunched from a closed state.

My root view controller sets the initial state of UINavigationBar like so:

struct MyRootView: View {

            init() {
                UINavigationBar.appearance().backgroundColor = getPreferredThemeColour() 
                UINavigationBar.appearance().largeTitleTextAttributes = [
                    .foregroundColor: UIColor.white]
            }

var body: some View {
...

getPreferredThemeColour() simply returns the desired UIColour from UserDefaults

My theme selector has buttons that change the colour attributes like so:

 Button(action: {
 UserDefaults.standard.set(UIColor.blue, forKey: "theme")
 UINavigationBar.appearance().backgroundColor = UIColor.blue }) {

I can't seem to find any way to refresh UINavigationBar "on the fly" to reflect the changes made. The app always needs to be relaunched.

Any help would be hugely appreciated!!!! Thank you.

1 Answers

Appearance applied to the instances created after appearance itself. So the solution is to recreate NavigationView after appearance changed.

Here is a worked demo of approach. Tested with Xcode 11.4 / iOS 13.4

demo

struct DemoNavigationBarColor: View {
    @State private var force = UUID()

    init() {
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.backgroundColor = UIColor.systemRed

        UINavigationBar.appearance().standardAppearance = navBarAppearance
    }

    var body: some View {
        VStack {
            NavigationView(){
                List(1 ... 20, id: \.self) { item in
                    NavigationLink(destination:
                        Text("Details \(item)")
                    ) {
                        Text("Row \(item)")
                    }
                }
                .navigationBarTitle("Title", displayMode: .inline)

            }.id(force)  // recreate NavigationView

            Button("Be Green") {
                UINavigationBar.appearance().standardAppearance.backgroundColor = UIColor.systemGreen
                self.force = UUID() // << here !!
            }
        }
    }
}
Related