SwiftUI: How can i change navigation bar style dynamic using custom modifier? - swift

I want to customize my navigation bar in SwiftUI. For that, i created a custom modifier, to set the backgroundColor and textColor and hide the 1px bottom line. The modifier looks something like this:
struct NavigationBarColor: ViewModifier {
init(backgroundColor: UIColor, tintColor: UIColor, lineHidden: Bool) {
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithOpaqueBackground()
coloredAppearance.backgroundColor = backgroundColor
coloredAppearance.titleTextAttributes = [.foregroundColor: tintColor]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: tintColor]
if lineHidden == true {
coloredAppearance.shadowImage = UIImage()
coloredAppearance.shadowColor = UIColor.clear
coloredAppearance.backgroundImage = UIImage()
}
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = tintColor
}
func body(content: Content) -> some View {
content
}
}
extension View {
func navigationBarColor(backgroundColor: UIColor, tintColor: UIColor, lineHidden: Bool = false) -> some View {
self.modifier(NavigationBarColor(backgroundColor: backgroundColor, tintColor: tintColor, lineHidden: lineHidden))
}
}
I use it on my ContentView() like this:
var body: some View {
NavigationView {
NavigationLink(destination: EventDetailView()) {
Text("Go to Detail Page")
}
.navigationTitle("Master")
.navigationBarColor(backgroundColor: .secondarySystemBackground, tintColor: .black, lineHidden: true)
}
}
It works as i expected, but when i set the modifier different on my detailView, the navigationBarAppearance is not updated. My DetailView looks like this:
var body: some View {
Text("Details")
.navigationTitle("Detail Page")
.navigationBarTitleDisplayMode(.inline)
.navigationBarColor(backgroundColor: .systemRed, tintColor: .white, lineHidden: true)
}
What can I change so that the style of the navigation bar changes when I go to the details page? Everhting an idea?

Use this which is a little different than your solution.
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
init(backgroundColor: UIColor, tintColor: UIColor, lineHidden: Bool) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = .clear
coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
if lineHidden == true {
coloredAppearance.shadowImage = UIImage()
coloredAppearance.shadowColor = UIColor.clear
coloredAppearance.backgroundImage = UIImage()
}
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = .white
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(backgroundColor: UIColor, tintColor: UIColor, lineHidden: Bool) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, tintColor: tintColor, lineHidden: lineHidden))
}
}
You may change the appears of the navigation bars.
Credit goes to https://filipmolcik.com/navigationview-dynamic-background-color-in-swiftui/

Related

How to dynamically change UINavigationBar color in Swiftui

I am working in SwiftUI, and I am trying to get Navigation Bar to update its color when a button is pressed.
I see that there are related questions, but it only allows for static colors in the navigation view.
In the provided example, I only see that the toolbar changes when the tab is changed after the button is pressed.
Is there a way to have the view update without changing tabs (which I believe requires creating a new navigationView).
struct ContentView: View {
var body: some View {
TabView {
First()
.tabItem{
Image(systemName: "1.square")
}
Second()
.tabItem{
Image(systemName: "2.square")
}
Third()
.tabItem{
Image(systemName: "3.square")
}
}
}//:Body
}//:ContentView
struct First: View {
var body: some View {
NavigationView {
HStack {
Button(action: {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = UIColor(.red)
UINavigationBar.appearance().barTintColor = UIColor(.red)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}, label: {
Text("red")
})//:Button
Button(action: {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = UIColor(.blue)
UINavigationBar.appearance().barTintColor = UIColor(.blue)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}, label: {
Text("blue")
})//:Button
}
.toolbar(content: {
ToolbarItem(placement: .principal, content: {
Text("Hello World 1")
})
})
}//:NavView
}
}
struct Second: View {
var body: some View {
NavigationView {
ScrollView {
Text("Don't use .appearance()!")
}
.navigationBarTitle("Try it!", displayMode: .inline)
.background(NavigationConfigurator { nc in
nc.navigationBar.barTintColor = .green
nc.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.white]
})
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct NavigationConfigurator: UIViewControllerRepresentable {
var configure: (UINavigationController) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
if let nc = uiViewController.navigationController {
self.configure(nc)
}
}
}
struct Third: View {
#State var navigationBackground: UIColor = UIColor(.gray)
var body: some View {
NavigationView {
HStack {
Spacer()
Button(action: {
navigationBackground = UIColor(.purple)
}, label: {
Text("purple")
.foregroundColor(.purple)
})//:Button
Spacer()
Button(action: {
navigationBackground = UIColor(.black)
}, label: {
Text("black")
.foregroundColor(.black)
})//:Button
Spacer()
}
.toolbar(content: {
ToolbarItem(placement: .principal, content: {
Text("Hello World 1")
})
})
}//:NavView
.navigationBarColor(backgroundColor: navigationBackground, titleColor: UIColor(.red))
.navigationBarTitleDisplayMode(.inline)
}
}
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: UIColor?, titleColor: UIColor?) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = backgroundColor
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UIToolbar.appearance().barTintColor = backgroundColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(backgroundColor: UIColor?, titleColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
}
}
The solutions that I tried are derivatives of answers from this post: SwiftUI update navigation bar title color
I just found a solution much later here
SwiftUI: Update Navigation Bar Color
The thing that seems to make everything work to force the change is an id property on the navigation view.
This worked for my use case.

iOS 15 Navigation Bar

I'm having this issue with the Navigation Bar in SwiftUI, where I don't want the Second View to have the ContentView's Navigation Bar. The preview in the SecondView behaves normal, but once I run the simulator and using the NavigationLink to go to SecondView, this is what it looks like.
import SwiftUI
struct ContentView: View {
init() {
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = UIColor(
red: 41/255,
green: 59/255,
blue: 77/255,
alpha: 1)
coloredAppearance.shadowColor = .clear
coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = .white
}
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Go to Second View")
.navigationTitle("SwiftUI")
}
}
}
}
struct SecondView: View {
var body: some View {
NavigationView {
Text("Second View")
.navigationBarTitleDisplayMode(.inline)
.ignoresSafeArea()
}
}
}
struct SecondView: View {
var body: some View {
NavigationView {
Text("Second View").navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
}.navigationBarHidden(true)
}
}
I have tested this and it removes the navigation bar completely

SwiftUI: Own ViewModifier doesn't refresh view

In my project I have two color pickers that change the background color and the font color.
Sheet, where I change the Color:
#ObservedObject var listColor: ListColor
ColorPicker("Hintergrund", selection: $listColor.bgColor)
ColorPicker("Text", selection: $listColor.textColor)
ContentView, where the change should be displayed:
#ObservedObject private var listColor = ListColor()
VStack{
VStack{...}
.backgroundColor(listColor.bgColor)
.foregroundColor(listColor.textColor)
}
.navigationBarTitle(Text("Workout"), displayMode: .automatic)
.navigationBarColor(backgroundColor: listColor.bgColorNav, titleColor: listColor.textColorNav) // my own viewmodifier
.navigationBarItems(trailing:
Button(action: {
self.showSettings.toggle()
}) {
Text("Settings")
}
.sheet(isPresented: $showSettings){
SettingsView(listColor: listColor) //open View with the color pickers
})
I also have my own ViewModifer that changes the background color and the font color of the navigation bar.
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: UIColor?, titleColor: UIColor?) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = backgroundColor
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
The problem is that the "normal" background and font color are changed, but not in the navigation bar. I think the problem is that my own ViewModifier for the navbar does not reload the view. I save the colors in the UserDefaults; when I start the app again, the changes are shown in the navigationbar.
Instead of using a normal variable inside your ViewModifier, declare a binding to a Color object. Than, if your wrapped value changes, the View (which is in this case the NavigationBar) will automatically redrawn.
My working example:
import SwiftUI
struct NavigationBarModifier: ViewModifier {
var backgroundColor: Binding<Color>
init(backgroundColor: Binding<Color>) {
self.backgroundColor = backgroundColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
self.backgroundColor.wrappedValue
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(_ bgColor: Binding<Color>) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: bgColor))
}
}
struct ContentView: View {
#State private var bgColor = Color(.sRGB, red: 0.98, green: 0.9, blue: 0.2)
var body: some View {
NavigationView {
ColorPicker("NavigationBar background color", selection: $bgColor)
.navigationBarTitle("Title", displayMode: .large)
.navigationBarColor(self.$bgColor)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI - Navigation bar colour change not being applied to status bar

I have the following:
var body: some View {
NavigationView {
VStack {
Text("Hello")
}.navigationBarTitle("Edit Profile", displayMode: .inline)
.background(NavigationConfiguration { nc in
nc.navigationBar.barTintColor = .red
nc.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.black]
})
}.navigationViewStyle(StackNavigationViewStyle())
}
For the configuration of the nav bar i have:
struct NavigationConfiguration: UIViewControllerRepresentable {
var configuration: (UINavigationController) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfiguration>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfiguration>) {
if let nc = uiViewController.navigationController {
self.configuration(nc)
}
}
}
But for some reason the top status bar is being left out and the colour is not being applied to it:
How can the red colour also be applied to the status bar above the nav bar, I want it to be the same colour.
I've tried the below, but this fails:
init() {
UINavigationBar.appearance().backgroundColor = .red
}
Try the following (it should work, at least as tested with Xcode 11.4 / iOS 13.4, but someone reported it was not, so it might be dependent)
init() {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.backgroundColor = UIColor.systemRed
UINavigationBar.appearance().standardAppearance = navBarAppearance
}
EDIT 1:
Checkout my new answer: https://stackoverflow.com/a/62031788/8023700
It has detailed description and screenshot as well.
Try using this modifier as this gives you freedom to change color based on each view.
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor = .clear
init(backgroundColor: UIColor, tintColor: UIColor = .white) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.backgroundColor = backgroundColor
coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = tintColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}}
I used it on below View:
struct DummyNavigationView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Page 2")) {
Text("Go to detail")
}
}
.navigationBarTitle("Edit Profile", displayMode: .inline)
}
.modifier(NavigationBarModifier(backgroundColor: .red, tintColor: .black))
}}

Updating the UINavigationBar color only applies a tint, not sure why?

So....I have a lot of code, and in an initialization of a view, I change the UINavigationBar:
struct ContentView: View {
init() {
UINavigationBar.appearance().backgroundColor = .black
}
var body: some View {
NavigationView {
VStack {
Text("Test")
}.navigationBarTitle("TestBarTitle", displayMode: .inline)
}
}
}
For some reason, this only makes the bar appear gray (almost like it's applying a transparent black filter). I am not sure but I think there must be some code that is messing up this navigation bar change. What might have possibly caused this? I'd like the navBar to literally be black.
Side Note: When I remove displayMode: .inline, the navBar appears as a solid color instead of transparent...how do I maintain the navBar setup in the way that displayMode: .inline provides though?
We can create a custom modifier called ".navigationBarColor()" and use it like so:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Test")
}
.navigationBarTitle("TestBarTitle", displayMode: .inline)
.navigationBarColor(.black)
}
}
}
Add this to your ContentView file:
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
init( backgroundColor: UIColor?) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = .clear
coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = .white
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(_ backgroundColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor))
}
}
Check out this article which was posted on March 10, 2020.
https://filipmolcik.com/navigationview-dynamic-background-color-in-swiftui/