Search code examples
iosswiftuinavigationcontrolleruinavigationbarswift4.2

How to change navigationBar bar button items color which is different from the initial color?


I've navigated from view A to view B (with new navigation controller and black tinted navigation bar)

View A controller:

    // With Navigation Controller
    let storyBoard: UIStoryboard = UIStoryboard(name: "ViewB", bundle: nil)
    let newViewController = storyBoard.instantiateViewController(withIdentifier: "ViewB") as! ViewBController
    let navCont = UINavigationController(rootViewController: newViewController)
    // Change the navigation bar to translucent
    navCont.navigationBar.setBackgroundImage(UIImage(), for: .default)
    navCont.navigationBar.shadowImage = UIImage()
    navCont.navigationBar.isTranslucent = true
    navCont.navigationBar.tintColor = UIColor.black
    //present(navCont, animated: true, completion: nil)
    show(navCont, sender: nil)

When navigating from view B to view C, I would like to change the navigationBar.tintColor from black to white.

View B Controller:

@IBAction func staticQRBtnPressed(_ sender: Any) {
    // Without Navigation Controller
    let storyBoard: UIStoryboard = UIStoryboard(name: "ViewC", bundle: nil)
    let newViewController = storyBoard.instantiateViewController(withIdentifier: "ViewCController") as! ViewCController
    newViewController.navigationController?.navigationBar.barTintColor = UIColor.white
    newViewController.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
    self.show(newViewController, sender: nil) // Push to navigation stack
}

View C controller:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    navigationController?.navigationBar.barTintColor = UIColor.white
    navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
}

Why are the above methods not working?


Solution

  • Beside why it's not working, it's not interactive. So it's will not work correctly if when user swipes to back and not synced with the navigation animations. So if you want a working interactive solution, here we go:

    Define a protocol for custom color:

    /// Navigation bar colors for `ColorableNavigationController`, called on `push` & `pop` actions
    public protocol NavigationBarColorable: class {
        var navigationTintColor: UIColor? { get }
        var navigationBarTintColor: UIColor? { get }
    }
    
    public extension NavigationBarColorable {
        var navigationTintColor: UIColor? { return nil }
    }
    

    Subclass UINavigationController and override navigation methods:

    /**
     UINavigationController with different colors support of UINavigationBar.
     To use it please adopt needed child view controllers to protocol `NavigationBarColorable`.
     - note: Don't forget to set initial tint and barTint colors
     */
    class AppNavigationController: UINavigationController {    
    
        override func viewDidLoad() {
            super.viewDidLoad()
            navigationBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
            navigationBar.shadowImage = UIImage()
            if let colors = rootViewController as? NavigationBarColorable {
                setNavigationBarColors(colors)
            }
        }
    
        private var previousViewController: UIViewController? {
            guard viewControllers.count > 1 else {
                return nil
            }
            return viewControllers[viewControllers.count - 2]
        }
    
        override open func pushViewController(_ viewController: UIViewController, animated: Bool) {
            if let colors = viewController as? NavigationBarColorable {
                setNavigationBarColors(colors)
            }
    
            setTabBarHidden(viewController is TabBarHidable)
    
            super.pushViewController(viewController, animated: animated)
        }
    
        override open func popViewController(animated: Bool) -> UIViewController? {
            if let colors = previousViewController as? NavigationBarColorable {
                setNavigationBarColors(colors)
            }
    
            setTabBarHidden(previousViewController is TabBarHidable)
    
            // Let's start pop action or we can't get transitionCoordinator()
            let popViewController = super.popViewController(animated: animated)
    
            // Secure situation if user cancelled transition
            transitionCoordinator?.animate(alongsideTransition: nil, completion: { [weak self] context in
                guard let `self` = self else { return }
                self.setTabBarHidden(self.topViewController is TabBarHidable)
                guard let colors = self.topViewController as? NavigationBarColorable else { return }
                self.setNavigationBarColors(colors)
            })
    
            return popViewController
        }
    
        override func popToRootViewController(animated: Bool) -> [UIViewController]? {
            if let colors = rootViewController as? NavigationBarColorable {
                setNavigationBarColors(colors)
            }
    
            let controllers = super.popToRootViewController(animated: animated)
    
            return controllers
        }
    
        private func setNavigationBarColors(_ colors: NavigationBarColorable) {
    
            if let tintColor = colors.navigationTintColor {
                navigationBar.titleTextAttributes = [
                    .foregroundColor : tintColor,
                    .font : R.font.iranSansFaNumBold(size: 14)!
                ]
                navigationBar.tintColor = tintColor
            }
    
            navigationBar.barTintColor = colors.navigationBarTintColor
        }
    }
    

    And conform and implement protocol methods in each view controller you want a custom navigation color for:

    extension MyCustomViewController: NavigationBarColorable {
        public var navigationBarTintColor: UIColor? { return .red }
        public var navigationTintColor: UIColor? { return .blue }
    }
    

    Note1: I added text color change support.

    Note2: I used one of my projects code for the base of this answer, So sorry if you see some none general naming and etc.

    Note3: As I mentioned in the code, Don't forget to set initial tint and barTint colors.