The app I\'m working on changes the barTintColor
of its navigation bar when pushing new view controllers. Right now we set that colour in the destination view contr
Here is a simpler fix. The issue with barTintColor
not animating correctly on pop occurs when you try to set the navigation bar appearance in viewWillDisappear
. The fix is to set it in willMove(toParentViewController:)
instead.
The code below will produce a smooth fading transition during both push and pop, and regardless of whether it is initiated by a gesture or button tap. Tested on iOS 10 and 11.
This also works for animating barStyle
.
import UIKit
class RedViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
self.title = "Red"
self.navigationController?.navigationBar.barTintColor = .red
self.navigationController?.navigationBar.tintColor = .white
}
override func willMove(toParentViewController parent: UIViewController?) {
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.tintColor = nil
}
}
To get a smooth animation during both push and pop, I had to make the navigation bar transparent and animate my own background color view behind it.
import Foundation
import UIKit
class ColorTransitionNavigationController: UINavigationController {
var navigationBarBackgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Put a background view behind the navigation bar
navigationBarBackgroundView = UIView()
view.insertSubview(navigationBarBackgroundView, belowSubview: navigationBar)
// Make the navigation bar transparent
navigationBar.isTranslucent = true
navigationBar.setBackgroundImage(UIImage(), for: .default)
// Size the colored background to match the navigation bar
navigationBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
navigationBarBackgroundView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
navigationBarBackgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
navigationBarBackgroundView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
// I used a hard-coded 64 instead of constraining to the height of the navigation bar because
// when calling navigationController.setNavigationBarHidden(true), the height of the navigation bar becomes 0
navigationBarBackgroundView.heightAnchor.constraint(equalToConstant: 64.0).isActive = true
}
func setBarTintColor(color: UIColor, animated: Bool, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
guard let transitionCoordinator = transitionCoordinator, animated else {
navigationBarBackgroundView.backgroundColor = color
return
}
transitionCoordinator.animateAlongsideTransition(in: view, animation: { [weak self] (context) in
let transition = CATransition()
transition.duration = context.transitionDuration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
self?.navigationBarBackgroundView.layer.add(transition, forKey: nil)
self?.navigationBarBackgroundView.backgroundColor = color
}, completion:nil)
}
}
If you want a UIViewController to animate the navigation bar color when it appears, override viewWillAppear
and call setBarTintColor
.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let navigationController = navigationController as? ColorTransitionNavigationController else { return }
navigationController.setBarTintColor(color: UIColor.green, animated: animated, transitionCoordinator: transitionCoordinator)
}
You can add extra animations that match the timing and animation curve of the view controller transition using UIViewControllerTransitionCoordinator.
A view controller's transitionCoordinator
will be set after a view controller's animation has started (so in viewWillAppear
of the presented view controller). Add any extra animations using animateAlongsideTransition:completion:
on the transition coordinator.
An example:
[[self transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.navigationController.navigationBar.translucent = NO;
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
self.navigationController.navigationBar.barTintColor = [UIColor redColor];
} completion:nil];