Search code examples
iosuinavigationcontrolleruibarbuttonitemuinavigationitem

Why doesn't overriding a View Controller's navigationItem seem to do anything?


I've now looked at the TONS of posts that are about removing the text from the back button on a UINavigationBar. They don't seem to work anymore (iOS 10+). And even if they did, they don't "smell correct"...

My question is, why doesn't this work?

class MyViewController: UIViewController {

    private var _navItem: UINavigationItem? = nil
    override var navigationItem: UINavigationItem {
        if _navItem == nil {
            let item = super.navigationItem
            let backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
            backBarButtonItem.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .normal)
            backBarButtonItem.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .highlighted)
            item.backBarButtonItem = backBarButtonItem
            _navItem = item
        }
        return _navItem!
    }
    //...
}

I would expect to see a button that has clear text, but I see the title of the previous view controller as the back button text.

Do I need to subclass UINavigationController? or mess around with UINavigationBarDelegate?


Solution

  • This is an extremely common point of confusion, since the API is a bit counterintuitive on its face. The documentation that explains it is a little convoluted as well

    When this navigation item is immediately below the top item in the stack, the navigation controller derives the back button for the navigation bar from this navigation item. When this property is nil, the navigation item uses the value in its title property to create an appropriate back button.

    Basically, you need to do this on the "previous view controller" not the one that you see the back button on.

    Though I'm not sure what your overall goals are, to do it "app wide", you have a couple options.

    • Put this into some sort of global base view controller class for your entire project. This is particularly cumbersome if you want to have different base classes for different parts of your apps, and of course when dealing with different types of system controllers.

    • UIAppearance methods, which have the downside (or upside?) of affecting every single bar button item, including the back button. This solution is generally too broad, though it is undeniably global.

      UIBarButtonItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .normal)
      UIBarButtonItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .highlighted)
      
    • Provide a custom left bar button item on the top view controller that you want the back button on. This solution again requires a base class of sorts, or good 'ol copy-pasta. And of course, loosing the ability to have the system back chevron provided for you. The alternative being an extension (Gabriel's answer provides a lovely example of that).

    Everything has tradeoffs.