88

Problems demo

Pre-conditions to reproduce the problem:

  1. Xcode 11 beta + iOS 13 (latest version until Jun. 12 2019)
  2. The navigation bar is in Large text mode
  3. Specify the colour of navigation bar.

The status bar will remain in white in a real device, above the green navigation bar.

Solutions I tried:

  1. Revert it back to iOS12 will solve it, but we will encounter iOS13 eventually...
  2. disabling the large text mode will solve it...
  3. hide the status bar will fix it, but it will cause status text overlapping with navigation bar item.

Any ideas? appreciate any help.

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
steven
  • 1,237
  • 2
  • 11
  • 13

18 Answers18

230

No hacks or funkiness required here. The key is defining the desired appearance and setting this value on BOTH the nav bar's standardAppearance AND its scrollEdgeAppearance. I have the following in the init for my base navigation controller subclass for my entire app:

if #available(iOS 13.0, *) {
    let navBarAppearance = UINavigationBarAppearance()
    navBarAppearance.configureWithOpaqueBackground()
    navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
    navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
    navBarAppearance.backgroundColor = <insert your color here>
    navigationBar.standardAppearance = navBarAppearance
    navigationBar.scrollEdgeAppearance = navBarAppearance
}

enter image description here

Mike
  • 9,765
  • 5
  • 34
  • 59
  • Your example code uses a custom `Theme` extension on UIColor, which makes it a bit hard to understand – Ely Aug 19 '19 at 07:56
  • 3
    This method has one issue about when user changing their theme (eg. light -> dark ) when app running in background. Other stuff changes its color but only navigation bar tint color remains. You can easily see that. Just run app in simulator and change appearence from Developer setting in Settings app. tried overriding `traitCollectionDidChange` and still got error. any ideas on that? – CenoX Aug 21 '19 at 16:26
  • 2
    @CenoX , I found quick and easy solution for your problem. First , create new color set in assets catalog. Change appearances from "None" to "Any, Light, Dark". Choose whatever colors u want. Second step is to load your custom UIColor in code. Use "let customColor = UIColor(named "CustomColor") , change name"CustomColor" to your asset name. Last step is to assign the color to our bar background. – ShadeToD Sep 22 '19 at 10:54
  • I think that is very good solution @ShadeToD. I'm gonna try to my application, Thanks. – CenoX Sep 22 '19 at 20:50
  • 1
    Add in a call to self.navigationController?.navigationBar.setNeedsLayout() if you're doing this after viewDidLoad() – bgolson Nov 18 '19 at 16:23
  • 1
    Obligatory "This is stupid, Apple" comment – user Jan 07 '21 at 04:56
21

If the problem is that you'd like to give the navigation bar a color when the large title is showing, use the new UINavigationBarAppearance class.

let app = UINavigationBarAppearance()
app.backgroundColor = .blue
self.navigationController?.navigationBar.scrollEdgeAppearance = app
pkamb
  • 33,281
  • 23
  • 160
  • 191
matt
  • 515,959
  • 87
  • 875
  • 1,141
17

Universal code

let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.backgroundColor = // your color
navBarAppearance.shadowImage = nil // line
navBarAppearance.shadowColor = nil // line
UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self]).standardAppearance = navBarAppearance
UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self]).scrollEdgeAppearance = navBarAppearance
pkamb
  • 33,281
  • 23
  • 160
  • 191
cvb
  • 1,715
  • 18
  • 22
  • 1
    Don't forget about `compactAppearance`! – Sebastian Sep 08 '19 at 11:04
  • 1
    Better use these three UINavigationBar.appearance() statements for universal usage: `UINavigationBar.appearance().standardAppearance = navBarAppearance UINavigationBar.appearance().compactAppearance = navBarAppearance UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance ` – Jim B Sep 25 '19 at 12:38
  • No need to set `compactAppearance` as "If not set, the standardAppearance will be used instead." – Ric Santos Mar 29 '20 at 22:04
16

On iOS 13, navigation bars using large title have a transparent color per Apple human interface guidelines. See more infos here:

In iOS 13 and later, a large title navigation bar doesn’t include a background material or shadow by default. Also, a large title transitions to a standard title as people begin scrolling the content

Hans Knöchel
  • 11,422
  • 8
  • 28
  • 49
12

Objective C Solutions and iOS 13

UINavigationBarAppearance* navBarAppearance = [self.navigationController.navigationBar standardAppearance];
        [navBarAppearance configureWithOpaqueBackground];
        navBarAppearance.titleTextAttributes = @{NSForegroundColorAttributeName:TitleColor};
        navBarAppearance.largeTitleTextAttributes = @{NSForegroundColorAttributeName: TitleColor};
        navBarAppearance.backgroundColor = TopColor;
        self.navigationController.navigationBar.standardAppearance = navBarAppearance;
        self.navigationController.navigationBar.scrollEdgeAppearance = navBarAppearance;
mohammad alabid
  • 444
  • 7
  • 21
12

my navigationBar extension, iOS 13 Swift 5

extension UIViewController {
func configureNavigationBar(largeTitleColor: UIColor, backgoundColor: UIColor, tintColor: UIColor, title: String, preferredLargeTitle: Bool) {
    if #available(iOS 13.0, *) {
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.largeTitleTextAttributes = [.foregroundColor: largeTitleColor]
        navBarAppearance.titleTextAttributes = [.foregroundColor: largeTitleColor]
        navBarAppearance.backgroundColor = backgoundColor

        navigationController?.navigationBar.standardAppearance = navBarAppearance
        navigationController?.navigationBar.compactAppearance = navBarAppearance
        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance

        navigationController?.navigationBar.prefersLargeTitles = preferredLargeTitle
        navigationController?.navigationBar.isTranslucent = false
        navigationController?.navigationBar.tintColor = tintColor
        navigationItem.title = title

    } else {
        // Fallback on earlier versions
        navigationController?.navigationBar.barTintColor = backgoundColor
        navigationController?.navigationBar.tintColor = tintColor
        navigationController?.navigationBar.isTranslucent = false
        navigationItem.title = title
    }
}}

How to use:

configureNavigationBar(largeTitleColor: .yourColor, backgoundColor: .yourColor, tintColor: .yourColor, title: "YourTitle", preferredLargeTitle: true)

Set ViewController-based status bar...... to NO in info.plist if you want light Content

If you don't want largeTitles set it to false

Tested on iOS 13, hope this help :)

Fabio
  • 5,432
  • 4
  • 22
  • 24
  • 1
    Thank you very much man.I wish i could give you more like for this. – Emon Nov 24 '19 at 19:08
  • 1
    @Emon Thx! I'm happy to have helped you :) – Fabio Nov 25 '19 at 11:02
  • While this approach works, it is not the best idea to set NavigationBar properties using the UINavigationBarAppearance for each UIViewController instance, as the UINavigationBarAppearance is shared (there is just one appearance at time) - this can lead to rather ugly effects in cases where both Controllers are visible, such as when you perform (slow) swipe-left-edge-to-go-back navigation gesture. – Pavel Lahoda Feb 15 '20 at 20:43
  • @PavelLahoda Your answer is correct, but the new OS creates some problems to show large titles ... The correct approach sometimes does not work and above all very rarely you use the swipe to go back when using the navigation controller but the button created automatically in the child controller ... However to be more secure just disable the swipe to go back (which in iOS is useless and not very functional). In all cases, thanks for your comment, it will certainly be useful for those who will read us later :) – Fabio Feb 16 '20 at 22:10
  • If you're changing these values dynamically in the app, like letting the user select his own app theme, then you will need to call `navigationController?.navigationBar.layoutSubviews()` in order to apply the changes instantly. – Starsky Jan 28 '21 at 09:17
5

If you want to remove the underline below the nav bar

if #available(iOS 13.0, *) {
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
        navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
        navBarAppearance.backgroundColor = <yourColor>
        navBarAppearance.backgroundImage = UIImage()
        navBarAppearance.shadowImage = UIImage()
        navBarAppearance.shadowColor = .clear
        self.navigationController?.navigationBar.standardAppearance = navBarAppearance
        self.navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
   
    }
Mirza Q Ali
  • 477
  • 5
  • 6
4

For iOS 13 I was having a problem with the bar's shadow line showing up. Setting the Bars shadow image to nil solved that issue.

Before

func configureNavigation() {
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.myColor,
                                                     .font: UIFont(name: "MyFont", size: 42)!]
        navBarAppearance.backgroundColor = .white
        navigationController?.navigationBar.isTranslucent = false
        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
    }

Image with shadow

After

func configureNavigation() {
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.myColor,
                                                     .font: UIFont(name: "MyFont", size: 42)!]
        navBarAppearance.backgroundColor = .white
        navBarAppearance.shadowColor = nil
        navigationController?.navigationBar.isTranslucent = false
        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
    }

Image without shadow

Andrew Edwards
  • 1,005
  • 1
  • 9
  • 24
3

Fully workable code:

 let navigationBarAppearace = UINavigationBar.appearance()
    navigationBarAppearace.tintColor = .tintColor
    navigationBarAppearace.barTintColor = .barTintColor
    navigationBarAppearace.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.tintColor]

if #available(iOS 13.0, *) {
  let navBarAppearance = UINavigationBarAppearance()
  navBarAppearance.configureWithOpaqueBackground()
  navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.tintColor]
  navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.tintColor]
  navBarAppearance.backgroundColor = <insert your color here>
  navigationBarAppearace.standardAppearance = navBarAppearance // have a look here ;)
  navigationBar.scrollEdgeAppearance = navBarAppearance
}

Good luck all, Peace!

Mihail Salari
  • 1,471
  • 16
  • 17
1

Thanks to Mike and Hans's answer. My case is half transparent status bar and nav bar with alpha 0.5. iOS13 seems complicated. Below is my test result, will work if you want transparent for both.

if #available(iOS 13.0, *) {
                let navBarAppearance = UINavigationBarAppearance()
                // This only set top status bar as transparent, not the nav bar.
                navBarAppearance .configureWithTransparentBackground()
                // This set the color for both status bar and nav bar(alpha 1).
                navBarAppearance.backgroundColor = UIColor.red.withAlphaComponent(0.5)
                navigationController?.navigationBar.standardAppearance = navBarAppearance
                navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
                // Nav bar need sets to translucent for both nav bar and status bar to be translucent.
                navigationController?.navigationBar.isTranslucent = true
                // // Need to reset nav bar's color to make it clear to display navBarAppearance's color
                navigationController?.navigationBar.backgroundColor = UIColor.clear
               } 
Ning
  • 148
  • 1
  • 10
  • Makes no sense to set the appearance background color to red and then make the background color clear. – matt Aug 08 '19 at 19:55
1

I had a similar problem when updating one of my apps to be more iOS 13 compatible. As Hans mentioned above, the large titles are transparent by default. If you are a heavy Storyboard user as I am, there's another setting in the side bar that's easy to turn on.

If you click on your nav bar in the story board, it usually defaults to selecting the Navigation Item, and you won't get any customization options. Select the Navigation Bar option above it, and then you are able to choose a custom background color of whatever you want over in the Inspector on the right.

enter image description here

Drew
  • 1,422
  • 1
  • 18
  • 29
0

I've discovered that with storyboards you have to fake the nav bar (only really works with opaque nav bars, assuming your green is opaque). The best way I found was to create a placeholder view (purple) that fits the safe area insets, and then add a fake view behind the nav bar (cyan/blue) that is the height remaining. Works for my project, but yeah it's a bit of a hack. Screenshot from Xcode 11 beta 4 displaying constraints required for status bar hack

Edit: This is mainly for LaunchScreen.storyboard where you can't use a custom view controller class.

SunburstEnzo
  • 173
  • 11
0

Swift 5

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)

    let userInterfaceStyle = traitCollection.userInterfaceStyle
    modeDetect(userInterfaceStyle: userInterfaceStyle)

}

override func viewDidAppear(_ animated: Bool) {
    navigationController?.navigationBar.barStyle = .black
    navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
}

func modeDetect(userInterfaceStyle: UIUserInterfaceStyle) {
    switch userInterfaceStyle {
    case .light:
        navigationController?.navigationBar.barTintColor = .systemPink
    case .dark:
        navigationController?.navigationBar.barTintColor = .systemBackground
    default:
        break
    }
}
Dumex
  • 1
  • 1
0

The following Objective-C code made iOS 13 navigation bar behave like the previous version for me:

    if (@available(iOS 13.0, *)) {
        // Setup iOS 13 navigation bar
        sharedSelector.navigationBar.scrollEdgeAppearance = sharedSelector.navigationBar.standardAppearance;
    } else {
        // Fallback on earlier versions
    }
André Pinto
  • 94
  • 1
  • 5
0

Call this function with a proper argument. This code is working properly.

open func showNavigationBar(large: Bool,
                            animated: Bool,
                            isTransparabar: Bool,
                            titleColor: UIColor,
                            barBackGroundColor: UIColor,
                            fontSize: CGFloat) {

        navigationController?.navigationBar.barTintColor = barBackGroundColor
        navigationController?.navigationBar.backgroundColor = barBackGroundColor
        navigationController?.navigationBar.isTranslucent = true
        self.navigationController?.setNavigationBarHidden(false, animated: animated)
        if large {
            self.navigationController?.navigationBar.prefersLargeTitles = true
            if #available(iOS 13.0, *) {
                let appearance = UINavigationBarAppearance()
                appearance.backgroundColor = barBackGroundColor
                appearance.titleTextAttributes = [.foregroundColor: titleColor]
                appearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: titleColor]

                navigationController?.navigationBar.standardAppearance = appearance
                navigationController?.navigationBar.compactAppearance = appearance
                navigationController?.navigationBar.scrollEdgeAppearance = appearance
            } else {
                self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: titleColor]
            }
        } else {
            self.navigationController?.navigationBar.prefersLargeTitles = false
            self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: titleColor!]
        }
    }
Kiran Sarvaiya
  • 1,318
  • 1
  • 13
  • 37
0

I just turn on translucent in storyboard

like here

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
Medef
  • 1
0

@mohmmad alabid's answer would need to be applied to each view controller seperately, if you want a generic objective-c solution put this in your app delegate inside the didFinishLaunchingWithOptions function.

if (@available(iOS 13.0, *)) {
    UINavigationBarAppearance* navBarAppearance = [[UINavigationBarAppearance alloc] init];
    [navBarAppearance configureWithOpaqueBackground];
    navBarAppearance.titleTextAttributes = @{NSForegroundColorAttributeName:NAVBAR_TEXT_COLOR};
    navBarAppearance.largeTitleTextAttributes = @{NSForegroundColorAttributeName: NAVBAR_TEXT_COLOR};
    navBarAppearance.backgroundColor = NAVBAR_COLOR;
    [[UINavigationBar appearance] setStandardAppearance: navBarAppearance];
    [[UINavigationBar appearance] setScrollEdgeAppearance: navBarAppearance];
}
James Wolfe
  • 340
  • 5
  • 18
-1

Mike's solution is great.

I offer an alternative approach to change the UINavigationBar color which applies to any iOS version.

We are basically going to exploit the fact that we can set an Image as the UINavigationBar's background.

FIRST

Add an extension to generate a UIImage from any UColor. Note that you can also also write an extension later to generate UIColor from hexadecimals or other formats if you want.

extension UIColor {
    func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
        return UIGraphicsImageRenderer(size: size).image { rendererContext in
            self.setFill()
            rendererContext.fill(CGRect(origin: .zero, size: size))
        }
    }

}

Thanks @neoneye for this great UIColor extension :)

SECOND

Now we get down to business:

private func setupNavigationBarAppearance(navBar: UINavigationBar) {
    navBar.isTranslucent = false
    let navBarColorImage = UIColor.blue.image()
    navBar.setBackgroundImage(navBarColorImage, for: .default)
    navBar.tintColor = UIColor.white
}

Because we've setup the opaque colored image as the background, we do not need to check for iOS 13.

I hope this help some folks to customize their NavBars.

Cheers!

BelfDev
  • 306
  • 5
  • 7
  • 1
    I just implemented the conventional approach via Xcode 11, but I like your thinking. :) – benc Jun 03 '20 at 06:38