Wednesday, October 25, 2017

How do I transition/animate color of UINavigationBar?

Leave a Comment

I have been searching for how to transition/animate the barTintColor of a UINavigationBar for a while now, and I only see different answers. Some use UIView.animateWithDuration, some use CATransition, but the most interesting ones, like this one use animate(alongsideTransition animation.., which I like the sound of, but I can't get it working properly. Am I doing something wrong?

Many specify that I can simply use the transitionCoordinator in viewWillAppear:. I have set up a fresh super tiny project like this:

class RootViewController:UIViewController{ //Only subclassed     override func viewWillAppear(_ animated: Bool) {         super.viewWillAppear(animated)         transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in             self?.setNavigationColors()             }, completion: nil)     }     func setNavigationColors(){         //Override in subclasses     } }  class FirstViewController: RootViewController {     override func viewDidLoad() {         super.viewDidLoad()         self.title = "First"     }     override func setNavigationColors(){         navigationController?.navigationBar.barTintColor = UIColor.white         navigationController?.navigationBar.tintColor = UIColor.black         navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.black]         navigationController?.navigationBar.barStyle = UIBarStyle.default     } } class SecondViewController: RootViewController {     override func viewDidLoad() {         super.viewDidLoad()         self.title = "Second"     }     override func setNavigationColors(){         navigationController?.navigationBar.barTintColor = UIColor.black         navigationController?.navigationBar.tintColor = UIColor.white         navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]         navigationController?.navigationBar.barStyle = UIBarStyle.black     } } 

With this code, this happens: First

  • The push-transition from First to Second looks perfect. All elements transition perfectly, maybe except the StatusBar, which instantly changes to white. I'd rather know how to transition it, but I'll accept it for now.
  • The pop-transition from Second to First is completely wrong. It keeps the colors from Second until the transition is completely done.
  • The drag-transition from Second to First looks alright, when dragging all the way over. Again, the StatusBar instantly becomes black as soon as I start dragging, but I don't know if that's possible to fix.
  • The drag-transition from Second to First but cancelled mid-drag and returning to Second is completely screwed up. It looks fine until Second is completely back in control, and then it suddenly changes itself to First-colors. This should not happen.

I made a few changes to my RootViewController to make it a little better. I removed viewWillAppear: completely, and changed it with this:

class RootViewController:UIViewController{      override func willMove(toParentViewController parent: UIViewController?) {         if let last = self.navigationController?.viewControllers.last as? RootViewController{             if last == self && self.navigationController!.viewControllers.count > 1{                 if let parent = self.navigationController!.viewControllers[self.navigationController!.viewControllers.count - 2] as? RootViewController{                     parent.setNavigationColors()                 }             }         }     }     override func viewWillDisappear(_ animated: Bool) {         if let parent = navigationController?.viewControllers.last as? RootViewController{             parent.animateNavigationColors()         }     }     override func viewDidAppear(_ animated: Bool) {         self.setNavigationColors()     }      func animateNavigationColors(){         transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in             self?.setNavigationColors()             }, completion: nil)     }     func setNavigationColors(){         //Override in subclasses     } } 

With this updated code, I get this: Second

A few observations:

  • The transition from First to Second is the same
  • The pop-transition from Second to First is now animating correctly, except from the back-arrow, the back-text (and the statusBar, but yeah..). These are instantly changed to black. In the first gif, you could see that the back-arrow and the back-text also transitioned.
  • The drag-transition from Second to First also has this problem, the back-arrow and back-text are suddenly instantly black when starting. The barTint is fixed so that it doesn't get the wrong color when cancelling the drag.

What am I doing wrong? How am I supposed to do this?

What I want is to transition all elements smoothly. The tint of the back-button, the back-text, the title, the barTint, and the statusBar. Is this not possible?

3 Answers

Answers 1

You can overwrite the push and pop methods of UINavigationController to set the bar color. I've stored the bar color corresponding to a view controller in its navigation item with a custom subclass of UINavigationItem. The following code works for me in iOS 11 for full and for interactive transitions as well:

import UIKit  class NavigationItem: UINavigationItem {     @IBInspectable public var barTintColor: UIColor? }  class NavigationController: UINavigationController, UIGestureRecognizerDelegate {     func applyTint(_ navigationItem: UINavigationItem?) {         if let item = navigationItem as? NavigationItem {             self.navigationBar.barTintColor = item.barTintColor         }     }      override func viewWillAppear(_ animated: Bool) {         super.viewWillAppear(animated)         applyTint(self.topViewController?.navigationItem)         self.interactivePopGestureRecognizer?.delegate = self     }     override func pushViewController(_ viewController: UIViewController, animated: Bool) {         applyTint(viewController.navigationItem)         super.pushViewController(viewController, animated: animated)     }      override func popViewController(animated: Bool) -> UIViewController? {         let viewController = super.popViewController(animated: animated)          applyTint(self.topViewController?.navigationItem)         return viewController     }      override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {         let result = super.popToViewController(viewController, animated: animated)          applyTint(viewController.navigationItem)         return result     }      override func popToRootViewController(animated: Bool) -> [UIViewController]? {         let result = super.popToRootViewController(animated: animated)          applyTint(self.topViewController?.navigationItem)         return result     }      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {         return true     }      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {         return (otherGestureRecognizer is UIScreenEdgePanGestureRecognizer)     } } 

Note: The coordination of the color animation is done by the navigation controller

Answers 2

I updated my previous answer. I did animation effect without using transition coordinator and they are smooth in every case like push/pop/swipe-back.

https://stackoverflow.com/a/40272975/5433235

Also, you can check it on my github project

Hope it helps you :)

Answers 3

See my answer on the same question: How to set navigation bar to transparent in iOS 11

You can set navbar to transparent and animate view below it.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment