Reputation: 755
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!
Upvotes: 3
Views: 2513
Reputation: 257663
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")
}
}
}
Upvotes: 4
Reputation: 30361
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:
Upvotes: 7