SwiftUI update navigation bar title color
Asked Answered
T

28

139

How to change the navigation bar title color in SwiftUI

NavigationView {
    List {
        ForEach(0..<15) { item in
            HStack {
                Text("Apple")
                    .font(.headline)
                    .fontWeight(.medium)
                    .color(.orange)
                    .lineLimit(1)
                    .multilineTextAlignment(.center)
                    .padding(.leading)
                    .frame(width: 125, height: nil)
                
                Text("Apple Infinite Loop. Address: One Infinite Loop Cupertino, CA 95014 (408) 606-5775 ")
                    .font(.subheadline)
                    .fontWeight(.regular)
                    .multilineTextAlignment(.leading)
                    .lineLimit(nil)
            }
        }
    }
    .navigationBarTitle(Text("TEST")).navigationBarHidden(false).foregroundColor(.orange)
}

I have tried with .foregroundColor(.orange) but it is not working

also tried .navigationBarTitle(Text("TEST").color(.orange))

Topee answered 8/6, 2019 at 10:29 Comment(2)
Hm.. it seems that swiftUI ignores any modifiers set for navigation bar title... And it's also strange that we cannot put any view in navigation bar :-(Heyerdahl
Faced similar problem and found a hacky way to do this by using ZStack and offsetting a Rectangle inside of it up off the screen underneath the navigation area. Check it out - https://mcmap.net/q/168083/-how-to-make-vstack-view-inside-scrollview-expand-up-and-take-the-space-under-navigation-title-while-keeping-the-content-in-the-same-spotAngeloangelology
E
174

It is not necessary to use .appearance() to do this globally.

Although SwiftUI does not expose navigation styling directly, you can work around that by using UIViewControllerRepresentable. Since SwiftUI is using a regular UINavigationController behind the scenes, the view controller will still have a valid .navigationController property.

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)
        }
    }

}

And to use it

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                Text("Don't use .appearance()!")
            }
            .navigationBarTitle("Try it!", displayMode: .inline)
            .background(NavigationConfigurator { nc in
                nc.navigationBar.barTintColor = .blue
                nc.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.white]
            })
        }
    .navigationViewStyle(StackNavigationViewStyle())
    }
}

Modified navigation bar

Ervin answered 17/10, 2019 at 8:4 Comment(27)
You should modify the value nc.navigationBar.setBackgroundImage(UIColor.blue.as1ptImage(), for: .default) to avoid translucent effect in dark themeKaohsiung
Does not work for subviews, even if you declare .background in said subview the navigation bar keeps the same color.Pied
with the scrollview the content goes under the navigation bar, and solution?Kaohsiung
it's great I do not know how it works but I assume similar approach can be possible for TabView customization or other UISegmentedController?Sneaky
@MichałZiobro Yes, the same technique should work. Just grab uiViewController.tabBarController instead of .navigationController.Ervin
Hmm, if this is the initial view set in the scene delegate it doesn't seem to work the first time; the vc.navigationController is nil in the closure. When I present a VC from somewhere it instantly reloads with the proper styling tho...Doglike
Hello. I used this and it worked but the color is little bit transparent. For example when I use black, it is not really black because of the white background color of the page it look little bit gray. Do you have a solution for that?Staciastacie
@Staciastacie You probably want to look at something like this answerErvin
@AndreaMiotto's comment is necessary if you wish to remove translucentcy. ".isTransclucent" crashes app.Coarsen
Like @Josh, I get nil for the NavigationController in updateUIViewController. However, nc stays nil whether it is called in initial view or returning elsewhere from navigation stack. Anyone find a solution?Fustic
Can something like this be used to modify a view's UINavigationItem, so as to, for example, replace the navigation bar title with a button view?Cyclostyle
the solution works but then for some reason I loose the displayMode: .automatic functionality, the title stays either always big or always compact, does anybody knows what might be the reason?Dunsinane
Hi @arsenius, do you know if this could be used to set hidesBarsOnSwipe? I gave it a try but it didn't work, not sure if I was missing something. Question is hereTeamwork
Unfortunately does not work with large display modeBarrybarrymore
How do I implement this? I have very little to no knowledge on Swift :cDeafen
maybe this is what @Pied meant with "subviews" in their earlier comment, but I wanted to clarify that this works for me in the view that declares a NavigationView; if I do it a view that was displayed via a NavigationLink, nothing changes even though the code is executedSpinescent
Using a viewModifier instead of UIViewControllerRepresentable as described in filipmolcik.com/… solves a lot of these issues like scrollView overlap, color not taking affect on first load, translucent effect and intermittent performance issues.Herringbone
@Barrybarrymore this will work for large title. let navBarAppearance = UINavigationBarAppearance(); navBarAppearance.configureWithOpaqueBackground(); navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]; navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]; navBarAppearance.backgroundColor = UIColor.red; nc.navigationBar.standardAppearance = navBarAppearance; nc.navigationBar.scrollEdgeAppearance = navBarAppearance sourceHitch
@Herringbone I've found out how to make it have color on first load (when large titles). Just add a nc.navigationBar.barTintColor = .blue after my previous comment. This color will be overridden by navBarAppearance, but it forces the navigation bar to redraw its color.Hitch
I have no idea why it doesn't work for me, but I implemented the view as sheet. The navigation bar title showed up but the navbar title color and navbar bar tint color doesn't change.Fiske
Edit: I implemented it on ContentView which also have NavigationView on the top, but it doesn't work. but this time the NavigationView contains ZStack and not ScrollView.Fiske
the most updated solution, I got the vc.navigationController nil too just like the other, I copy all the code perfectly to make sure nothing wrong but still the navbar title color and navbar bar tint color doesn't change.. then @Herringbone answer solve my problem. this sourceFiske
@Barrybarrymore to change background color for large title, just set nc.navigationBar.backgroundColor = .redTremolant
This didn't work for me either, but @EngageTheWarpDrive response worked. The problem here is that on the method updateUIViewController the UIViewController was not added to the UINavigationController yet, so we need to wait a bit more to get it. That's way the answer from @EngageTheWarpDrive worked.Hajji
See my solution that adds a hack to solve the issues mentioned.Madonna
@BrunoCoelho #65404691 you have to change the content in func makeUIViewController() by let controller = UIViewController() if let navigationContoller = controller.navigationController { self.configure(navigationContoller) } return controllerHootenanny
UIViewControllerRepresentable is the absolute worstFeatherveined
A
57

In SwiftUI, you can not change the navigationTitleColor directly. You have to change UINavigation's appearance in init() like this,

struct YourView: View {

    init() {
        //Use this if NavigationBarTitle is with Large Font
        UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.red]

        //Use this if NavigationBarTitle is with displayMode = .inline
        UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.red]
    }

    var body: some View {

        NavigationView {
            List{
                ForEach(0..<15) { item in
                    HStack {
                        Text("Apple")
                            .font(.headline)
                            .fontWeight(.medium)
                            .color(.orange)
                            .lineLimit(1)
                            .multilineTextAlignment(.center)
                            .padding(.leading)
                            .frame(width: 125, height: nil)


                        Text("Apple Infinite Loop. Address: One Infinite Loop Cupertino, CA 95014 (408) 606-5775 ")
                            .font(.subheadline)
                            .fontWeight(.regular)
                            .multilineTextAlignment(.leading)
                            .lineLimit(nil)
                    }
                }
            }
            .navigationBarTitle(Text("TEST")).navigationBarHidden(false)
            //.navigationBarTitle (Text("TEST"), displayMode: .inline)
        }
    }
}

I hope it will work. Thanks!!

Arapaho answered 23/8, 2019 at 17:25 Comment(8)
Do you know how one can do this using SwiftUI on WatchOS?Diphosgene
I get an error: Use of unresolved identifier 'UINavigationBar'Diphosgene
NavigationView is not available in watchOS.Arapaho
Check your post #58035841 , i answered there how you can change it.Arapaho
This works globally and will effect all other views in the app.Pied
I'm having an issue where I can't have a binding as well as the init. Is there a way to have both? Exact error message: "Return from initializer without initializing all stored properties"Avitzur
I've learned that if you use init, then you have to init all the variables. How do you correctly init an @Binding? Reason for the error: #29673988Avitzur
Also, if I add "self.show = show" like you would in any init, then it gives me the error "'self' used before all stored properties are initialized". That error goes away if I change self.show form an @Binding to a constant (let)Avitzur
C
53

I have searched for this issue and find a great article about this, you could wrap the settings of navigation bar style as a view modifier.

Check this Link.

Notes: I believe you need to update some code in this example, add titleColor parameter.

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()
                }
            }
        }
    }
}

extension View {

    func navigationBarColor(backgroundColor: UIColor?, titleColor: UIColor?) -> some View {
        self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
    }

}

After that, apply like this:

.navigationBarColor(backgroundColor: .clear, titleColor: .white)

I hope it will work.

Coonhound answered 1/6, 2020 at 4:10 Comment(7)
Thanks for the solution :) Works amazingly! But when the Navigation Bar title switches to inline appearance , the bar height remains doesn't reduce? Have you tried on any workaround for that? Thanks in advance :DVenditti
This should be the accepted answer. Using UIViewControllerRepresentable causes a lot of issues like overlapping scrollView and sometimes not changing color at all.Herringbone
This is great! The original article should have coloredAppearance.backgroundColor = backgroundColor instead of coloredAppearance.backgroundColor = .clear, as you did -- because of the rubber banding scroll effect.Hitch
This solution works great for modifying the global navigation bar. Has anyone been able to make the style change when navigating to sub-Views using this solution?Barabbas
If the user changes between light and dark mode while using your app, the bar color will not change with this. I recommend adding another argument to the modifier called style with type UIUserInterfaceStyle and assigning the appearance for the init(userInterfaceStyle:) of UITraitCollection. That way, if the user switches between light and dark mode while using you app the navigation bar color changes too. You will need one modifier for each style thoughHolusbolus
This applies the color globally. might as well do this in AppDelegate without the whole shenanigans here..Minnesinger
I was looking for the solution for a long time. Thanks for the answer :) Only one issue pending. I want to have different config for scroll. If I set different object for that then nothing works.Floorman
S
43

In iOS 14, SwiftUI has a way to customize a navigation bar with the new toolbar modifier.

We need to set ToolbarItem of placement type .principal to a new toolbar modifier. You can even set an image and much more.

NavigationView {
    Text("My View!")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .principal) {
                HStack {
                    Image(systemName: "sun.min.fill")
                    Text("Title")
                        .font(.headline)
                        .foregroundColor(.orange)
                }
            }
        }
}
Suffruticose answered 19/7, 2020 at 7:6 Comment(3)
But you would not be able to set the background color of the toolbar, would you?Shrill
This only works for displayMode .inline, as you have it here, but not for .largeArjan
This does not answer the question.Hyperesthesia
G
25

Building on the answer from Arsenius, I found that an elegant way to get it to work consistently was to subclass UIViewController and do the configuration in viewDidLayoutSubviews().

Usage:

VStack {
    Text("Hello world")
        .configureNavigationBar {
            $0.navigationBar.setBackgroundImage(UIImage(), for: .default)
            $0.navigationBar.shadowImage = UIImage()
        }
}

Implementation:

extension View {
    func configureNavigationBar(configure: @escaping (UINavigationController) -> Void) -> some View {
        modifier(NavigationConfigurationViewModifier(configure: configure))
    }
}

struct NavigationConfigurationViewModifier: ViewModifier {
    let configure: (UINavigationController) -> Void

    func body(content: Content) -> some View {
        content.background(NavigationConfigurator(configure: configure))
    }
}

struct NavigationConfigurator: UIViewControllerRepresentable {
    let configure: (UINavigationController) -> Void

    func makeUIViewController(
        context: UIViewControllerRepresentableContext<NavigationConfigurator>
    ) -> NavigationConfigurationViewController {
        NavigationConfigurationViewController(configure: configure)
    }

    func updateUIViewController(
        _ uiViewController: NavigationConfigurationViewController,
        context: UIViewControllerRepresentableContext<NavigationConfigurator>
    ) { }
}

final class NavigationConfigurationViewController: UIViewController {
    let configure: (UINavigationController) -> Void

    init(configure: @escaping (UINavigationController) -> Void) {
        self.configure = configure
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        if let navigationController = navigationController {
            configure(navigationController)
        }
    }
}
Grisgris answered 7/7, 2020 at 23:47 Comment(6)
This does fix the issue when navigation controller is nil on the first load. Thanks!Tremolant
This is definitely working better. Tested on iOS 14Sterner
Happy I could help!!Grisgris
Is there any way to make this work with .navigationBarTitleDisplayMode(.automatic)? It seems to only work with .large or .inline - never does the transition from large to inline when set to automatic.Dolorisdolorita
Apparently, starting with XCode 13, viewDidLayoutSubviews doesn't work but viewWillAppear does on iOS 14 🤷‍♂️Unable
This works well, and you can also use viewWillDisappear to reset any configuration in case you want to do that.Velmaveloce
P
25

I took a slightly different approach; I wanted to change only the title text color, and nothing else about the NavigationBar. Using the above and this as inspiration, I landed on:

import SwiftUI

extension View {
    /// Sets the text color for a navigation bar title.
    /// - Parameter color: Color the title should be
    ///
    /// Supports both regular and large titles.
    @available(iOS 14, *)
    func navigationBarTitleTextColor(_ color: Color) -> some View {
        let uiColor = UIColor(color)
    
        // Set appearance for both normal and large sizes.
        UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: uiColor ]
        UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: uiColor ]
    
        return self
    }
}

This requires iOS 14 because UIColor.init(_ color: Color) requires iOS 14.

Which can be leveraged as such:

struct ExampleView: View {
    var body: some View {
        NavigationView {
            Text("Hello, World!")
                .navigationBarTitle("Example")
                .navigationBarTitleTextColor(Color.red)
        }
    }
}

Which in turn yields:

Red navigation title

Pammy answered 4/2, 2021 at 17:32 Comment(2)
Could you please say where I should call this func? It gives me error "Cannot find 'self' in scope"Drawn
Love the elegance and effectiveness of the scope for this solution!Reinold
C
11

Demo

from iOS 14, You can have any custom view you want (including custom text with custom color and font)

.navigationBarTitleDisplayMode(.inline)
.toolbar {
    ToolbarItem(placement: .principal) {
        VStack {
            Text("Yellow And Bold Title")
                .bold()
                .foregroundColor(.yellow)
        }
    }
}

Also you can set the navigation bar color from the iOS 16 like:

.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(.red, for: .navigationBar)
Collectivity answered 16/6, 2022 at 8:41 Comment(2)
Yeah, but what if you need to support iOS 14, 15 and 16? How is your example applying red for versions before 16?Leflore
You should go with the appearance workaround for that @LefloreCollectivity
M
10

Instead of setting appearance(), which affects all navigation bars, you can set them individually using SwiftUI-Introspect.

Example:

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                Text("Hello world!")
            }
            .navigationTitle("Title")
        }
        .introspectNavigationController { nav in
            nav.navigationBar.barTintColor = .systemBlue
        }
    }
}

Result:

Result

Mathur answered 17/9, 2021 at 16:48 Comment(1)
This is actually a great suggestion, thanks! I spent days looking for a convenient way to customise the NavigationView title and this worked quite nicely 👍Hawse
F
8

Use Below Code for Color Customization in SwiftUI

This is for main body background color:-

struct ContentView: View {

var body: some View {

 Color.red
.edgesIgnoringSafeArea(.all)

 }

}

enter image description here

For Navigation Bar:-

struct ContentView: View {

@State var msg = "Hello SwiftUI😊"

init() {

    UINavigationBar.appearance().backgroundColor = .systemPink

     UINavigationBar.appearance().largeTitleTextAttributes = [
        .foregroundColor: UIColor.white,
               .font : UIFont(name:"Helvetica Neue", size: 40)!]

}

var body: some View {

    NavigationView {

    Text(msg)

        .navigationBarTitle(Text("NAVIGATION BAR"))

    }

    }

  }

enter image description here

For Other UI Elements Color Customization

struct ContentView: View {

@State var msg = "Hello SwiftUI😊"

var body: some View {

        Text(msg).padding()
            .foregroundColor(.white)
            .background(Color.pink)

    }
 }

enter image description here

Fairyfairyland answered 2/2, 2020 at 10:30 Comment(1)
tks, setting largeTitleTextAttributes worked for meCallipygian
O
7

I have developed a small sample of a custom SwiftUI navigation that can provide full visual customisation and programatic navigation. It can be used as a replacement for the NavigationView.

Here is the NavigationStack class that deals with currentView and navigation stack:

final class NavigationStack: ObservableObject  {
    @Published var viewStack: [NavigationItem] = []
    @Published var currentView: NavigationItem

    init(_ currentView: NavigationItem ){
        self.currentView = currentView
    }

    func unwind(){
        if viewStack.count == 0{
            return
        }

        let last = viewStack.count - 1
        currentView = viewStack[last]
        viewStack.remove(at: last)
    }

    func advance(_ view:NavigationItem){
        viewStack.append( currentView)
        currentView = view
    }

    func home( ){
        currentView = NavigationItem( view: AnyView(HomeView()))
        viewStack.removeAll()
    }
}

You can have a look here: for the full example with explanation:

PS: I am not sure why this one was deleted. I think it answer the question as it is a perfect functional alternative to NavigationView.

Offspring answered 13/8, 2019 at 15:56 Comment(0)
P
6
init() {
    // for navigation bar title color
    UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor:UIColor.red]
   // For navigation bar background color 
    UINavigationBar.appearance().backgroundColor = .green
}

NavigationView {
       List {
           ForEach(0..<15) { item in
               HStack {
                    Text("Apple")
                       .font(.headline)
                       .fontWeight(.medium)
                       .color(.orange)
                       .lineLimit(1)
                       .multilineTextAlignment(.center)
                       .padding(.leading)
                       .frame(width: 125, height: nil)

                    Text("Apple Infinite Loop. Address: One Infinite Loop Cupertino, CA 95014 (408) 606-5775 ")
                       .font(.subheadline)
                       .fontWeight(.regular)
                       .multilineTextAlignment(.leading)
                       .lineLimit(nil)
                }
           }
       }
       .navigationBarTitle(Text("TEST")).navigationBarHidden(false)
}
Plemmons answered 27/6, 2019 at 7:30 Comment(4)
Thank you , I know appearance api can do it (see my comments) I need swift ui version of solutionTopee
Doesn't seem to work if the view is presented modally.Tanbark
@MycroftCanner that's because the modal doesn't inherit the root navigation view.Pied
@Pied but I've implemented NavigationView {} on the top of the modal viewFiske
N
6

Based on this https://mcmap.net/q/165817/-swiftui-update-navigation-bar-title-color I created an extension where you can set the background color and the title color at the same time.

import SwiftUI

extension View {
    
    /// Sets background color and title color for UINavigationBar.
    @available(iOS 14, *)
    func navigationBar(backgroundColor: Color, titleColor: Color) -> some View {
        let appearance = UINavigationBarAppearance()
        appearance.configureWithTransparentBackground()
        appearance.backgroundColor = UIColor(backgroundColor)

        let uiTitleColor = UIColor(titleColor)
        appearance.largeTitleTextAttributes = [.foregroundColor: uiTitleColor]
        appearance.titleTextAttributes = [.foregroundColor: uiTitleColor]

        UINavigationBar.appearance().standardAppearance = appearance
        UINavigationBar.appearance().scrollEdgeAppearance = appearance
        
        return self
    }
}

Here's how to use it:

var body: some View {
    NavigationView {
        Text("Hello world!") // This could be any View (List, VStack, etc.)
        .navigationTitle("Your title here")
        .navigationBar(backgroundColor: .blue, titleColor: .white)
    }
}

Happy coding!

Nike answered 14/5, 2022 at 12:44 Comment(2)
This is for global appearance, thus it doesn't make sense to have a view extension for it as it setting global state.Barcroft
@Barcroft I agree.Gigi
U
4

If you have your content as

struct MyContent : View {
...
}

then you can put it like this inside a navigation view with a red background:

NavigationView {
    ZStack(alignment: .top) {
        Rectangle()
            .foregroundColor(Color.red)
            .edgesIgnoringSafeArea(.top)
        MyContent()
    }
}

I will update my answer as soon as I know how to update the title text itself.

Unskilled answered 13/6, 2019 at 13:20 Comment(0)
T
4

Definitely there are already a few good answers, but all of them will cover only part of the job:

  1. Great solution from @arsenius - give the good point to start

  2. Elegant way from @EngageTheWarpDrive - this definitely improve usability

  3. For latest version of iOS and swiftUI @Thahir suggest to use toolbar

Few more suggestions propose to use UIAppearence global config for UINavigationBar - as for me global change is not a good idea and may be not always suitable.

I ended up combining all proposals in to the next code:

  1. Create NavigationControllerRepresentable and modifier for navigationBar configuration:

     struct NavigationControllerLayout: UIViewControllerRepresentable {
    
         var configure: (UINavigationController) -> () = { _ in }
    
         func makeUIViewController(
             context: UIViewControllerRepresentableContext<NavigationControllerLayout>
         ) -> UIViewController {
             UIViewController()
         }
    
         func updateUIViewController(
             _ uiViewController: UIViewController,
             context: UIViewControllerRepresentableContext<NavigationControllerLayout>
         ) {
             if let navigationContoller = uiViewController.navigationController {
                 configure(navigationContoller)
             }
         }
     }
    
     extension View {
    
         func configureNavigationBar(_ configure: @escaping (UINavigationBar) -> ()) -> some View {
             modifier(NavigationConfigurationViewModifier(configure: configure))
         }
     }
    
     struct NavigationConfigurationViewModifier: ViewModifier {
    
         let configure: (UINavigationBar) -> ()
    
         func body(content: Content) -> some View {
             content.background(NavigationControllerLayout(configure: {
                 configure($0.navigationBar)
             }))
         }
     }
    
  2. To modify navigationBar to meet u'r requirements (such as bg color and other props):

    extension UINavigationBar {
    
         enum Appearence {
    
             case transparent
             case defaultLight
             case colored(UIColor?)
    
             var color: UIColor {
                 ...
             }
    
             var appearenceColor: UIColor {
                 ...
             }
    
             var tint: UIColor {
                 ....
             }
    
             var effect: UIBlurEffect? {
                ....
             }
         }
    
         func switchToAppearence(_ type: Appearence) {
             backgroundColor = type.color
             barTintColor = type.tint
    
             // for iOS 13+
             standardAppearance.backgroundColor = type.appearenceColor
             standardAppearance.backgroundEffect = type.effect
    
             // u can use other properties from navBar also simply modifying this function
         }
     }
    
  3. As u can see, here we definitely need some bridge between Color and UIColor. Starting from iOS 14 - u can just UIColor.init(_ color: Color), but before iOS 14 there is not such way, so I ended up with simple solution:

     extension Color {
    
         /// Returns a `UIColor` that represents this color if one can be constructed
         ///
         /// Note: Does not support dynamic colors
         var uiColor: UIColor? {
             self.cgColor.map({ UIColor(cgColor: $0) })
         }
     }
    

this will not work for dynamic colors

  1. As result u can use this as following:

     // modifier to `NavigationView`
     .configureNavigationBar {
         $0.switchToAppearence(.defaultLight)
     }
    

Hopefully this may help to someone ;)

Tantrum answered 7/11, 2020 at 11:19 Comment(0)
S
3

Staring with iOS 16+, you can use a combination of toolbarBackground and toolbarColorScheme modifiers to achieve a level of customization to the navigation bar.

NavigationStack {
    ContentView()
        .toolbarBackground(Color.accentColor)
        .toolbarBackground(.visible)
        .toolbarColorScheme(.dark)
}

Credit where credit is due, I learned about this from Sarunw.

Shakira answered 23/7, 2023 at 6:29 Comment(0)
P
2

I still haven't figured out how to do the foreground color on a per-view basis, but I did figure out a simple workaround for the background color.

If using an .inline title, you can just use a VStack with a rectangle at the top of the NavigationView:

NavigationView {
    VStack() {
        Rectangle()
            .foregroundColor(.red)
            .edgesIgnoringSafeArea(.top)
            .frame(height: 0)

        List {
            Text("Hello World")
            Text("Hello World")
            Text("Hello World")
        }
    }
    .navigationBarTitle("Hello World", displayMode: .inline)
    // ...

Note how the rectangle uses a frame height of 0 and .edgesIgnoringSafeArea(.top).

Pied answered 9/11, 2019 at 2:14 Comment(0)
D
2

update for 13.4

note: revisiting this the next day, it may be possible that some of my issues were caused by my somewhat nonstandard setup: i am still running mojave, but have manually added the 13.4 support files (normally available only via xcode 11.4, which requires catalina). i mention this because i am/was also having some tab bar custom color issues, but i just noticed that those are only manifesting when i have the phone actually plugged in and am running the app from xcode. if i unplug, and just run the app normally, i am not seeing the tab bar issues, so it may be possible that the nav bar issue had some similarity ...

(i would add this as a comment on arsenius' answer (the currently accepted one) above, but i don't have the rep, so ...)

i was using that solution, and it was working perfectly up until 13.4, which seems to have broken it, at least for me. after a lot of view hierarchy tracing, it looks like they changed things such that the implicit UINavigationController is no longer easily accessible via the passed UIViewController as described in the workaround. it's still there though (pretty far up the tree), we just have to find it.

to that end, we can just walk the view hierarchy until we find the navbar, and then set the desired parameters on it, as usual. this necessitates a new discovery function, and some minor changes to the NavigationConfigurator struct, and its instantiation ...

first up, the discovery function:

func find_navbar(_ root: UIView?) -> UINavigationBar?
{
    guard root != nil else { return nil }

    var navbar: UINavigationBar? = nil
    for v in root!.subviews
    {   if type(of: v) == UINavigationBar.self { navbar = (v as! UINavigationBar); break }
        else { navbar = find_navbar(v); if navbar != nil { break } }
    }

    return navbar
}

modify the NavigationConfigurator as follows (note that we no longer care about passing in a view, since that's no longer reliable):

struct NavigationConfigurator: UIViewControllerRepresentable
{
    @EnvironmentObject var prefs: Prefs     // to pick up colorscheme changes

    var configure: () -> Void = {}
    func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController { UIViewController() }
    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfigurator>) { self.configure() }
}

(in my app, i have a Prefs object which keeps track of colors, etc.)

... then, at the instantiation site, do something like this:

MyView()
    .navigationBarTitle("List", displayMode: .inline)
    .navigationBarItems(trailing: navbuttons)
    .background(NavigationConfigurator {
        if self.prefs.UI_COLORSCHEME != Colorscheme.system.rawValue
        {   if let navbar = find_navbar(root_vc?.view)
            {   navbar.barTintColor = Colors.uicolor(.navbar, .background)
                navbar.backgroundColor = .black
                navbar.titleTextAttributes = [.foregroundColor: Colors.uicolor(.navbar, .foreground)]
                navbar.tintColor = Colors.uicolor(.navbar, .foreground)
            }
        }
    })

note that i capture the root view controller elsewhere in my app, and use it here to pass to find_navbar(). you might want to do it differently, but i already have that variable around for other reasons ... there's some other stuff there specific to my app, e.g., the color-related objects, but you get the idea.

Dock answered 8/4, 2020 at 8:1 Comment(0)
B
2

Here is the solution that worked for me. You need to start off with a UINavigationController as the rootViewController.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        let nav = setupNavigationController()
        window.rootViewController = nav
        self.window = window
        window.makeKeyAndVisible()
    }
}

func setupNavigationController() -> UINavigationController {
    let contentView = ContentView()
    let hosting = UIHostingController(rootView: contentView)
    let nav = NavigationController(rootViewController: hosting)
    let navBarAppearance = UINavigationBarAppearance()
    navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
    navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
    navBarAppearance.backgroundColor = UIColor.black
    nav.navigationBar.standardAppearance = navBarAppearance
    nav.navigationBar.scrollEdgeAppearance = navBarAppearance
    nav.navigationBar.prefersLargeTitles = true
    return nav
}

and then in your content view:

struct ContentView: View {

    @State private var isModalViewPresented: Bool = false

    var body: some View {
        List(0 ..< 10, rowContent: { (index) in
            NavigationLink(destination: DetailView()) {
                Text("\(index)")
            }
        })
        .navigationBarItems(trailing: Button("Model") {
            self.isModalViewPresented.toggle()
        })
        .sheet(isPresented: $isModalViewPresented, content: {
            ModalView()
        })
        .navigationBarTitle("Main View")
    }
}

and if you want to change the color at some point, such as in a modal view, use the answer given here

struct ModalView: View {
    var body: some View {
        NavigationView {
           Text("Hello, World!")
           .navigationBarTitle("Modal View")
           .background(NavigationConfigurator { nc in
              nc.navigationBar.backgroundColor = UIColor.blue
              nc.navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
           })
       }
    }
}

you can subclass UINavigationController to change the status bar color

class NavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override var preferredStatusBarStyle: UIStatusBarStyle 
    {
        .lightContent
    }
}

Main View Modal View

Bicollateral answered 25/4, 2020 at 1:4 Comment(0)
A
2

.foregroundColor(.orange) - изменяет внутренние представления NavigationView.

But to change the navigation view itself, you need to use UINavigationBar Appearance() in init()

I was looking for this problem and found a great article about it. And i modified your code by this article and came to success. Here, how i solve this problem:

struct ContentView: View {
    
    init() {
        let coloredAppearance = UINavigationBarAppearance()
        
        // this overrides everything you have set up earlier.
        coloredAppearance.configureWithTransparentBackground()
        coloredAppearance.backgroundColor = .green
        coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.black]
        
        // to make everything work normally
        UINavigationBar.appearance().standardAppearance = coloredAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
    }
    
    var body: some View {
        NavigationView {
            List{
                ForEach(0..<15) { item in
                    HStack {
                        Text("Apple")
                            .font(.headline)
                            .fontWeight(.medium)
                            .lineLimit(1)
                            .multilineTextAlignment(.center)
                            .padding(.leading)
                            .frame(width: 125, height: nil)
                            .foregroundColor(.orange)
                        
                        
                        Text("Apple Infinite Loop. Address: One Infinite Loop Cupertino, CA 95014 (408) 606-5775 ")
                            .font(.subheadline)
                            .fontWeight(.regular)
                            .multilineTextAlignment(.leading)
                            .lineLimit(nil)
                            .foregroundColor(.orange)
                    }
                }
            }
            .navigationBarTitle(Text("TEST"))
        }
// do not forget to add this
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

You can also take some examples here

Advowson answered 9/4, 2021 at 17:14 Comment(0)
H
2

WatchOS navigation title color using SwiftUI
Side note for watchOS is that you don't need to fiddle with the navigation color. It's the Watch Accent color you need to change. In your project go into WatchProjectName->Asset->Accent and change this enter image description here

https://developer.apple.com/documentation/watchkit/setting_the_app_s_tint_color

Hercules answered 1/12, 2021 at 18:37 Comment(0)
M
1

https://mcmap.net/q/165817/-swiftui-update-navigation-bar-title-color this answer works, but if you are experiencing issues with navigationController being nil in light or dark mode. Just add this.. no idea why it works.

struct ContentView: View {
   var body: some View {
      NavigationView {
        ScrollView {
            Text("Don't use .appearance()!")
        }
        .navigationBarTitle("Try it!", displayMode: .inline)
        .background(NavigationConfigurator { nc in
            nc.navigationBar.barTintColor = .blue
            nc.navigationBar.background = .blue
            nc.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.white]
        })
    }
   .navigationViewStyle(StackNavigationViewStyle())
   .accentColor(.red)   <------- DOES THE JOB
  }
}
Martymartyn answered 15/1, 2021 at 12:45 Comment(1)
accentColor will also change the default color of buttons.Madonna
R
1

This solution builds on the accepted answer that doesn't use any library nor does it apply UINavigationBarAppearance globally.

This solution fixes the issues that the accepted answer has (such as not working for the initial view or not working for large display mode) by adding a hack.

Note I would personally not use this hack in production code, nevertheless it's interesting to see that the issues can be worked around. Use at own risk.

struct NavigationHackView: View {

    @State private var isUsingHack = false

    var body: some View {
        NavigationView {
            List {
                NavigationLink {
                    Text("Detail view")
                        .navigationTitle("Detail view")
                        .navigationBarTitleDisplayMode(.inline)
                } label: {
                    Text("Show details view")
                }
            }
            .navigationTitle("Hack!")
            .background(
                NavigationConfigurator { navigationController in
                    // required for hack to work
                    _ = isUsingHack
                    navigationController.navigationBar.navigationBarColor(.red, titleColor: .white)
                }
            )
            .onAppear {
                // required for hack to work
                DispatchQueue.main.async {
                    isUsingHack.toggle()
                }
            }
            // required for hack to work, even though nothing is done
            .onChange(of: isUsingHack) { _ in }
        }
    }
}

struct NavigationConfigurator: UIViewControllerRepresentable {

    var configure: (UINavigationController) -> Void = { _ in }

    func makeUIViewController(
        context: UIViewControllerRepresentableContext<NavigationConfigurator>
    ) -> UIViewController {
        UIViewController()
    }

    func updateUIViewController(
        _ uiViewController: UIViewController,
        context: UIViewControllerRepresentableContext<NavigationConfigurator>
    ) {
        guard let navigationController = uiViewController.navigationController else {
            return
        }
        configure(navigationController)
    }
}

extension UINavigationBar {

    func navigationBarColor(
        _ backgroundColor: UIColor, 
        titleColor: UIColor? = nil
    ) {
        let appearance = UINavigationBarAppearance()
        appearance.configureWithOpaqueBackground()
        appearance.backgroundColor = backgroundColor

        if let titleColor = titleColor {
            appearance.titleTextAttributes = [.foregroundColor: titleColor]
            appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
            // back button appearance
            tintColor = titleColor
        }

        standardAppearance = appearance
        scrollEdgeAppearance = appearance
        compactAppearance = appearance

        if #available(iOS 15.0, *) {
            compactScrollEdgeAppearance = appearance
        }
    }
}
Raychel answered 5/4, 2022 at 14:43 Comment(0)
L
1

It is annoying to use NavigationView. From iOS 16. Instead of using NavigationTitle, you can use .toolbar to custom your title, it would be more flexible.

enter image description here enter image description here

For instance:

import SwiftUI

struct Company: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

struct ContentView: View {
    private let companies: [Company] = [
        .init(name: "Google"),
        .init(name: "Apple"),
        .init(name: "Amazon"),
        .init(name: "Huawei"),
        .init(name: "Baidu")
    ]
    
    @State private var path: [Company] = []
    var body: some View {
        NavigationStack(path: $path) {
            List(companies) { company in
                NavigationLink(company.name, value: company)
            }
            .toolbar {
                    ToolbarItem(placement: .principal) {
                        Text("Navigation Test")
                            .foregroundColor(.yellow)
                    }
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Text("Settings")
                        .foregroundColor(.yellow)
                }
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Text("Discovery")
                        .foregroundColor(.yellow)
                }
            }
            .navigationBarTitleDisplayMode(.inline)
            .toolbarBackground(Color.red, for: .navigationBar)
            .toolbarBackground(.visible, for: .navigationBar)
            .navigationDestination(for: Company.self) { company in
                DetailView(company: company)
            }
        }
        .accentColor(.yellow)
        .tint(.green)
    }
}

DetailView.swift


import SwiftUI

struct DetailView: View {
    var company: Company
    
    var body: some View {
        Text(company.name)
            .toolbarBackground(Color.red, for: .navigationBar)
            .toolbarBackground(.visible, for: .navigationBar)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Text("Detail View")
                        .foregroundColor(.yellow)
                }
            }
    }
}

Lieselotteliestal answered 30/4, 2023 at 16:17 Comment(0)
F
0

The solution that worked for me was to use UINavigationBarAppearance() method, then add the .id() to the NavigationView. This will automatically redraw the component when the color changes.

Now you can have reactive color changes based on a state engine.

var body: some Scene {
  let color = someValue ? UIColor.systemBlue : UIColor.systemGray3

  let custom = UINavigationBarAppearance()
  custom.configureWithOpaqueBackground()
  custom.backgroundColor = color
  UINavigationBar.appearance().standardAppearance = custom
  UINavigationBar.appearance().scrollEdgeAppearance = custom
  UINavigationBar.appearance().compactAppearance = custom
  UINavigationBar.appearance().compactScrollEdgeAppearance = custom

  return WindowGroup {
    NavigationView {
      content
    }
      .id(color.description)
  }
}
Firebrat answered 18/12, 2021 at 20:50 Comment(0)
C
0
Post iOS 14 easy way to do:

        protocol CustomNavigationTitle: View {
            associatedtype SomeView: View
            func customNavigationTitle(_ string: String) -> Self.SomeView
        }
        
        extension CustomNavigationTitle {
            func customNavigationTitle(_ string: String) -> some View {
                toolbar {
                    ToolbarItem(placement: .principal) {
                        Text(string).foregroundColor(.red).font(.system(size: 18))
                    }
                }
            }
        }
        
        extension ZStack: CustomNavigationTitle {}
    
    Suppose your root view of view is made with ZStack
    it can be utilised below way
    
    ZStack {
    }. customNavigationTitle("Some title")
Calliope answered 18/4, 2022 at 7:37 Comment(0)
V
0
 .toolbar {
            ToolbarItem(placement: .principal) {
                Text("Your Score \(count)")
                .foregroundColor(.white)
                .font(.largeTitle)
                .bold()
                .shadow(radius: 5, x: 0, y: -10)
            }
            
        }
Viceroy answered 2/7, 2023 at 11:1 Comment(0)
S
-1

The simplest way I found was:

init() {
    UINavigationBar.appearance().tintColor = UIColor.systemBlue
    }

instead of the systemBlue you can use any other colors that you wish. You have to implement this outside the "var body: some View {}". you can also add:

@Environment(/.colorScheme) var colorScheme

on top of the init() and then you can use the .dark or .light to change the color the way you want in dark mode and light mode. example:

init() {
    UINavigationBar.appearance().tintColor = UIColor(colorScheme == .dark ? .white : Color(#colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1)))
    }
Slavish answered 8/1, 2022 at 17:26 Comment(1)
This will apply this styling globally, not only for this view. Meaning that you don't even need to have this code in your Views.Madonna
V
-2

I have used ViewModifier to apply custom colour for navigation bar. I can't say below code modified actual navigation bar, but I find this work around better than above others.

Unlike UINavigationBar.appearance(), it is not applied to all view.

  • Create a ViewModifer - I have use ShapeStyle, so you can apply any style to navigation bar. (like - gradient, colour)
struct NavigationBarStyle<S: ShapeStyle>: ViewModifier {
    private var bgStyle: S
    private var viewBackgroundColor: Color
    
    init(_ bgStyle: S, viewBackgroundColor: Color) {
        self. bgStyle = bgStyle
        self.viewBackgroundColor = viewBackgroundColor
    }
    
    func body(content: Content) -> some View {
        ZStack {
            Color(UIColor.systemBackground)
                .ignoresSafeArea(.all, edges: .bottom)
            
            content
        }
        .background(bgStyle)
    }
}

extension View {
    func navigationBarStyle<S: ShapeStyle>(_ bgStyle: S, viewBackgroundColor: Color = Color(UIColor.systemBackground)) -> some View {
        modifier(NavigationBarStyle(bgStyle, viewBackgroundColor: viewBackgroundColor))
    }
}

Note - you have to apply this modifier on the top most view to work. e.g -

struct NewView: View {
    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    Text("H Stack")
                }
                // .navigationBarStyle(Color.orange) not the right place
                Text("Hello World")
            }
            .navigationBarStyle(Color.orange) // right place to apply
        }
    }
}

Virginiavirginie answered 18/6, 2022 at 20:7 Comment(1)
the answer has nothing about navigation barKatzenjammer

© 2022 - 2025 — McMap. All rights reserved.