Animate navigation bar barTintColor change in iOS10 not working
Asked Answered
E

2

12

I upgraded to XCode 8.0 / iOS 10 and now the color change animation of my navigation bar is not working anymore, it changes the color directly without any animation.

UIView.animateWithDuration(0.2, animations: {
    self.navigationController?.navigationBar.barTintColor = currentSection.color!
})

Anyone knows how to fix this?

Embarrassment answered 15/9, 2016 at 15:47 Comment(0)
H
38

To animate navigationBar’s color change in iOS10 you need to call layoutIfNeeded after setting color inside animation block.

Example code:

UIView.animateWithDuration(0.5) { 
    self.navigationController?.navigationBar.barTintColor = UIColor.redColor()
    self.navigationController?.navigationBar.layoutIfNeeded()
}

Also I want to inform that Apple doesn’t officialy support animations in such properties like barTintColor, so that method can break at any time.

If you call -layoutIfNeeded on the navigation bar during the animation block it should update its background properties, but given the nature of what these properties do, there really hasn't ever been any kind of guarantee that you could animate any of them.

Harpsichord answered 17/9, 2016 at 6:40 Comment(5)
It works indeed, but the title of my navigation bar is also animating (I don't set the title in the "animateWithDuration" function. The title move from the top left to the center of the navigation bar when the color change is animating...Embarrassment
@Tiois, I think that problem is only in your project. Possible you set title in same layout cycle, check all properties that you change in your ViewController. I have created test project and title stays at place, you can check it here: dl.dropboxusercontent.com/u/42855950/test.zipHarpsichord
You were right, after setting the title, I now call layoutIfNeeded() on the navigation and and then I call the UIView.animateWithDuration function with layoutIfNeeded() and it's working fine! Thanks.Embarrassment
You just saved my dayQuits
layoutIfNeeded() works amazingly, Thank you for saving the day. :)Oceania
B
4

Interactive animation

Interactive animation

Define a protocol:

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

public extension NavigationBarColorable {
    var navigationTintColor: UIColor? { return nil }
}

Define a custom NavigationController subclass:

class AppNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        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)
        }
               
        super.pushViewController(viewController, animated: animated)
    }
    
    override open func popViewController(animated: Bool) -> UIViewController? {
        if let colors = previousViewController as? NavigationBarColorable {
            setNavigationBarColors(colors)
        }
                        
        // 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 }

            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
            ]
            navigationBar.tintColor = tintColor
        }
        
        navigationBar.barTintColor = colors.navigationBarTintColor
    }
}

Now you can conform to NavigationBarColorable in any controller inside the AppNavigationController and give it any color you want.

extension FirstViewController: NavigationBarColorable {
    public var navigationBarTintColor: UIColor? { UIColor.red }
    public var navigationTintColor: UIColor? { UIColor.white }
}

extension SecondViewController: NavigationBarColorable {
    public var navigationBarTintColor: UIColor? { UIColor.blue }
    public var navigationTintColor: UIColor? { UIColor.orange }
}

Don't forget to implement this useful extension:

extension UINavigationController {
    var rootViewController: UIViewController? {
        return viewControllers.first
    }
}
Burtburta answered 20/10, 2019 at 10:19 Comment(1)
The best answer ever! Thanks.Grandfather

© 2022 - 2024 — McMap. All rights reserved.