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
Related
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.
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")
}
}
}
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()
}
}
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))
}}
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/