10

I have a new app that ideally will have a solid dark blue navigation bar at the top that extends up behind the status bar. It was a pain to make this opaque and the correct color of blue, but I finally figured out how to make it work by putting the following in the init() of my View that contains the NavigationView:

init() {
    UINavigationBar.appearance().barTintColor = UIColor(named: "primaryBlue")
    UINavigationBar.appearance().backgroundColor = UIColor(named: "primaryBlue")
    UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
    UINavigationBar.appearance().isOpaque = true
}

This results in a navigation bar that looks like this. It's blue, but the status bar is still white with black text (I want it to be dark blue with white text).

enter image description here

Next, I knew that I had to make the text of the status bar "light content", and found a good solution from Idiqual here, but this simply changes the color "theme" of the bar, and there doesn't appear to be a way to change the background color using this method. After implementation, I now have a status bar that is ready to show light text on a dark background, but I'm unable to figure out how to get the dark blue background of the NavigationView to extend to the top of the status bar. So what I'm left with is this:

enter image description here

I've tried several things, such as adding .edgesIgnoringSafeArea(.top) to several different places, including the closing bracket of the NavigationView and also the TabView that I have in the parent view, but nothing works. Am I missing something simple? How do I extend the NavigationBar's color to the top of the screen? Here is the code for my Nav view. This struct is called "FetchFrontPageArticles":

var body: some View {
    NavigationView {
        VStack {
            List(self.fetcher.articles) { article in
                ...
            }.navigationBarTitle("", displayMode: .inline)
            .navigationBarItems(
                leading: Text(""),
                trailing: NavProfile()
            )
        }
    }
}

"FetchFrontPageArticles" is loaded from the parent TabView shown here:

TabView(selection: $selection){
        FetchFrontPageArticles()
            .tabItem {
                VStack {
                    Image("house")
                    Text("Home")
                }
            }
            .tag(0)
        Text("Second View")
            .tabItem {
                VStack {
                    Image("list.dash")
                    Text("Browse")
                }
            }
            .tag(1)
        ...
    }
    .accentColor(Color("primaryYellow"))

I'm pretty stuck trying to resolve this, and it seems like it should be simple. Please help!

UPDATE: Per Kyle's answer below, I've already tried this approach. Here is a screenshot of my nav bar after implementing the NavConfigurator (notice the bar looks lighter blue, because the transparency effect comes into play):

enter image description here

eResourcesInc
  • 948
  • 1
  • 9
  • 17
  • Since posting, I've found that this occurs only if I have a List within my view. If I remove the list, then my app respects the .edgestIgnoreSafeArea modifier for the navigation bar. However, once you add a list, your status bar becomes white, since this is the background color of the list. You can change UITableView background color, but when you do that and scroll, you can still see the individual rows pass through the status bar as if it was clear. – eResourcesInc Dec 18 '19 at 16:41

4 Answers4

22

When I started coding with SwiftUI, I faced the same issue and after so much research I found the solution.

Put the below code in the SceneDelegate class.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {        
    let newAppearance = UINavigationBarAppearance()
    newAppearance.configureWithOpaqueBackground()
    newAppearance.backgroundColor = .black
    newAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]

    UINavigationBar.appearance().standardAppearance = newAppearance

    //Other code for displaying the first screen
}

UINavigationBarAppearance class is used for changing appearance of the navigation bar and it is available from iOS 13.

The above code will change the navigation bar style of all the controllers.

Indrajeet
  • 5,490
  • 2
  • 28
  • 43
  • 2
    This actually does what no other code I've found on the internet was able to do. Much appreciated. – eResourcesInc Dec 19 '19 at 18:34
  • 1
    This answer worked correctly when there is navigation bar. If you hide the bar in one view, then the status bar loses the background color (becomes transparent) – Alan Steiman May 13 '20 at 06:40
  • My app get crashed when I put this in Scene func. *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__SwiftValue set]: unrecognized selector sent to instance 0x2816809c0' – Zhou Haibo Jun 28 '20 at 11:57
  • `UINavigationBarAppearance` introduced in iOS13 works great. In addition to `UITabBar.standardAppearance` you might need to set `UITabBar.scrollEdgeAppearance`. – Jason Moore Feb 15 '22 at 13:11
7

The following works for me:

create an extension for UINavigationController which will change the following:

  • navigationbar background color
  • statusbar background color
  • navigationbar title color

    import UIKit
    
    extension UINavigationController {
        override open func viewDidLoad() {
            super.viewDidLoad()
    
            let appearance = UINavigationBarAppearance()
            //background color of the navigation and status bar
            appearance.backgroundColor = .black
            //color when the title is large
            appearance.largeTitleTextAttributes.updateValue(UIColor.white, forKey: NSAttributedString.Key.foregroundColor)
            //color when the title is small
            appearance.titleTextAttributes.updateValue(UIColor.white, forKey: NSAttributedString.Key.foregroundColor)
    
            // change the background- and title foregroundcolor for navigationbar
            navigationBar.standardAppearance = appearance
            navigationBar.scrollEdgeAppearance = appearance
            navigationBar.compactAppearance = appearance
            // change color of navigationbar items
            navigationBar.tintColor = UIColor.white
        }
    }
    

however this will not change the statusbar foreground color. for that we need to do the following:

import SwiftUI

class HostingController<Content> : UIHostingController<Content> where Content : View {
    @objc override dynamic open var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
}

and then in our scenedelegate

we need to replace

window.rootViewController = UIHostingController(rootView: contentView)

with

window.rootViewController = HostingController(rootView: contentView)
ARR
  • 2,074
  • 1
  • 19
  • 28
0

I have struggled on this problem for about one hour, finally I find that if you are using TabView, you need to add the edgesIgnoringSafeArea to TabView rather than the view in the tab.

TabView {
   ViewA().tabItem.....

}.edgesIgnoringSafeArea(.top)

hope it helps

cookiezby
  • 89
  • 2
  • 4
  • If I do this the navigationview goes under the notch – Joris Mans Mar 31 '20 at 20:11
  • In my situation, I use the TabView as my base view of the app, and navigation view is inside of the TabView. I am not sure if your navigationView is in TabView or not. – cookiezby Apr 01 '20 at 06:56
  • 1
    When your TabView is the base of your app, and `ViewA()` is a navigation view, you should add `.edgesIgnoringSafeArea(.top)` to `View()` in the above example. – JacobF Sep 07 '20 at 08:21
-1

Try using a UIViewControllerRepresentative:

NavigationView {
    VStack {

         // your stuff

    }
    .accentColor(.white)
    .background(NavConfigurator { nav in
                nav.navigationBar.barTintColor = UIColor(named: "primaryBlue")
                nav.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.white]
    })
}

Edit: Forgot representative code:

import SwiftUI

struct NavConfigurator: UIViewControllerRepresentable {

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

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

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavConfigurator>) {
        if let nc = uiViewController.navigationController {
            self.configure(nc)
        }
    }

}
Kyle Beard
  • 604
  • 5
  • 18
  • What is `NavConfigurator`? – LuLuGaGa Dec 18 '19 at 15:19
  • I've tried this. Same result, only my bar isn't even opaque in this case. I've also tried the ZStack approach seen in videos like this: https://www.youtube.com/watch?v=1pK1yZlzeTw. But for some reason, the zstack color is never visible over my status bar. I am editing my answer above to show what it looks like when I use this approach. – eResourcesInc Dec 18 '19 at 15:25