Search code examples
iosswiftuinavigationcontrolleruinavigationbar

Setting UINavigationBar appearance ignored in iOS 11


I'm trying to set the UINavigationBar appearance (tintColor, barTintColor etc) between screens, but at the moment in iOS 11 most of this seems to be completely ignored or doesn't behave as expected. The bar appearance changes inside a single navigation controller, when a view is pushed or popped. I have two functions, which I call in viewWillAppear.

I need to be able to set the title colour, the left and right bar button item colour, back button colour and bar tint colour.

I'm trying to get just the colours working at the moment, so I tried this, but no joy.

public func setDarkHeaderStyle() {
    UIApplication.shared.statusBarStyle = .lightContent

    UINavigationBar.appearance().tintColor = UIColor.white
    UINavigationBar.appearance().barTintColor = Colours.secondaryNavy
    UINavigationBar.appearance().isTranslucent = false
}

public func setLightHeaderStyle() {
    UIApplication.shared.statusBarStyle = .default

    UINavigationBar.appearance().tintColor = Colours.primaryNavy
    UINavigationBar.appearance().barTintColor = UIColor.white
    UINavigationBar.appearance().isTranslucent = false
}

Dark style Light style

If I instead use the navigation controller to set the colours, it does work for the bar tint, the UIBarButtonItem and the back button, but the title is incorrect.

public func setDarkHeaderStyle() {
    UIApplication.shared.statusBarStyle = .lightContent

    navigationController?.navigationBar.tintColor = UIColor.white
    navigationController?.navigationBar.barTintColor = Colours.secondaryNavy
    navigationController?.navigationBar.isTranslucent = false
}

public func setLightHeaderStyle() {
    UIApplication.shared.statusBarStyle = .default

    navigationController?.navigationBar.tintColor = Colours.primaryNavy
    navigationController?.navigationBar.barTintColor = UIColor.white
    navigationController?.navigationBar.isTranslucent = false
}

Dark style Light style

So I manually set the title text attributes with the following:

public func setDarkHeaderStyle() {
    UIApplication.shared.statusBarStyle = .lightContent

    navigationController?.navigationBar.titleTextAttributes = [
        NSAttributedStringKey.font: UIFont(name: Fonts.fontRegularName, size: 16)!,
        NSAttributedStringKey.kern: 0.2,
        NSAttributedStringKey.foregroundColor: UIColor.white
    ]

    navigationController?.navigationBar.tintColor = UIColor.white
    navigationController?.navigationBar.barTintColor = Colours.secondaryNavy
    navigationController?.navigationBar.isTranslucent = false
}

public func setLightHeaderStyle() {
    UIApplication.shared.statusBarStyle = .default

    navigationController?.navigationBar.titleTextAttributes = [
        NSAttributedStringKey.font: UIFont(name: Fonts.fontRegularName, size: 16)!,
        NSAttributedStringKey.kern: 0.2,
        NSAttributedStringKey.foregroundColor: Colours.primaryNavy
    ]

    navigationController?.navigationBar.tintColor = Colours.primaryNavy
    navigationController?.navigationBar.barTintColor = UIColor.white
    navigationController?.navigationBar.isTranslucent = false
}

This seems to work, except when you pop back to the root view the title colour isn't set:

Dark initial load Light initial load Dark popped

I guess I have two questions:

Why doesn't UINavigationBar.appearance() work? How can I get this working reliably?


Solution

  • I think this is a bug. UIBarNavigationItem for some reason seems to ignore your changes on its title attributes and tint color unless the text of your title changes. This is a weird behaviour and you might consider reporting it. A workaround could be to toggle an empty space suffix to your title:

    // Hack!!! adds and removes an empty space to the title to 
    // force the bar item reset title attributes.
    let title: String = barItem.title ?? ""
    barItem.title = title.hasSuffix(" ") ? String(title.dropLast()) : title + " "