swiftuithemesuinavigationbarappearance

Switching UINavigationBar Appearance in SwiftUI


I need to be able to change the colors in a SwiftUI View on demand. This is not an issue for switching colors on demand for objects in a View, but how would one be able to do that for the NavigationBar appearance properties? Here is how I set the Navigation Bar appearance when the view is initialized. Tapping the button changes the button colors, but not the Nav Bar appearance. Restarting the app with a different value for theme1 will show the correct colors, but tapping the button only changes the button colors and not the NavBar appearance.

struct ContentView: View {
@State private var theme1 = true
init() {
    let navBarAppearance = UINavigationBarAppearance()
    navBarAppearance.titleTextAttributes = [.foregroundColor: theme1 ? UIColor.red : UIColor.yellow]
    navBarAppearance.largeTitleTextAttributes = [.foregroundColor: theme1 ? UIColor.red : UIColor.yellow]
    navBarAppearance.backgroundColor = theme1 ? UIColor.yellow : UIColor.red
    UINavigationBar.appearance().standardAppearance = navBarAppearance
    UINavigationBar.appearance().compactAppearance = navBarAppearance
    UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance
    UINavigationBar.appearance().tintColor = theme1 ? UIColor.red : UIColor.yellow
}
var body: some View {
    NavigationView {
        VStack {
            Button("Toggle Style") {
                theme1.toggle()
            }
            .padding(8)
            .foregroundColor(theme1 ? Color(.red): Color(.yellow))
            .background(RoundedRectangle(cornerRadius: 10)
                            .fill(theme1 ? Color(.yellow) : Color(.red)))
        }
        .navigationTitle("Theme Picker")
    }
}

}


Solution

  • Because we are using UIKit in code, we have to kill View First then create the one we want:


    Version 1:

    import SwiftUI
    
    struct ContentView: View {
        @State private var theme1 = true
    
        var body: some View {
            
            if theme1 {
                CustomView(theme: $theme1)
            }
            else {
                CustomView(theme: $theme1)
            }
      
        }
    }
    

    struct CustomView: View {
        
        @Binding var theme1: Bool
        
        init(theme: Binding<Bool>) {
            
            _theme1 = theme
            
            let navBarAppearance = UINavigationBarAppearance()
            navBarAppearance.titleTextAttributes = [.foregroundColor: theme1 ? UIColor.red : UIColor.yellow]
            navBarAppearance.largeTitleTextAttributes = [.foregroundColor: theme1 ? UIColor.red : UIColor.yellow]
            navBarAppearance.backgroundColor = theme1 ? UIColor.yellow : UIColor.red
            UINavigationBar.appearance().standardAppearance = navBarAppearance
            UINavigationBar.appearance().compactAppearance = navBarAppearance
            UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance
            UINavigationBar.appearance().tintColor = theme1 ? UIColor.red : UIColor.yellow
        }
        
        var body: some View {
            NavigationView {
                VStack {
                    Button("Toggle Style") {
                        theme1.toggle()
                    }
                    .padding(8)
                    .foregroundColor(theme1 ? Color(.red): Color(.yellow))
                    .background(RoundedRectangle(cornerRadius: 10)
                                    .fill(theme1 ? Color(.yellow) : Color(.red)))
                }
                .navigationTitle("Theme Picker")
            }
        }
        
    }
    

    Version 2:

    This is a way that I would recommend: With this in mind that we should be responsible for navigationTitle ourself, like when it should be shown or hide or be small . . .


    import SwiftUI
    
    struct ContentView: View {
        
        @State private var theme1 = true
        
        var body: some View {
            
            NavigationView {
                
                ZStack {
                    
                    theme1 ? Color.yellow.ignoresSafeArea() : Color.red.ignoresSafeArea()
                    
                    Color.white.cornerRadius(20).padding()
                    
                    VStack {
                        
                        Button(action: { theme1.toggle() }, label: {
                            Text("Toggle Style")
                                .bold()
                                .padding(8)
                                .background(theme1 ? Color.yellow : Color.red)
                                .cornerRadius(10)
                        })
                        
                    }
                    
                }
                
            }
            .overlay(navigationTitle, alignment: .topLeading)
            .foregroundColor(theme1 ? Color.red : Color.yellow)
            .accentColor(theme1 ? Color.red : Color.yellow)
            
        }
        
        var navigationTitle: some View {
            
            return Text("Theme Picker").font(Font.largeTitle.bold()).padding().offset(y: 30)
        }
    
    }