Search code examples
iosiphoneswiftuinavigationcontrolleruinavigationbar

Interactive transition: navigation bar appearance issue


Lets's say, I have 3 view controllers: A, B, C, all embedded into a navigation controller. A and B have a navigation bar, C doesn't.

I have a custom interactive transition between B and C. Since I need my navigation bar to disappear on C, I implemented this function of UINavigationControllerDelegate:

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    if viewController is C {
        navigationController.setNavigationBarHidden(true, animated: animated)
    }
    else {
        navigationController.setNavigationBarHidden(false, animated: animated)
    }
}

Everything works perfectly in an common scenario, when I only make push-pop transitions.

But when I cancel transition B->C by calling cancel() on my UIPercentDrivenInteractiveTransition, the navigation bar doesn't show up on B. Here I have to call setNavigationBarHidden(false, ...), but I haven't managed to find the correct place to do it.

If I call it in B's viewWillAppear, navigation bar appears, but looks really strange - it contains elements which the C would have if it had navigation bar. If I pop back to the A, it will blink for a moment with expected contents, but immediately after transition the A navigation bar gets replaced by the B navigation bar!

So, it seems like the navigation bars stack is somehow broken after B->C transition cancellation, it appears to be shifted relatively to viewcontrollers like that:

                      has
-----------------------------------------------
|    ViewController    |   Navigation bar of  |
-----------------------------------------------
|           A          |           B          |
-----------------------------------------------
|           B          |           C          |
-----------------------------------------------

So, my question is what's the correct place to call navigationController.setNavigationBarHidden(false, animated: true) in this case?


Solution

  • Well, I've managed to find an ugly hack to fix it by myself. Maybe someone in this world would find it helpful.

    • In my custom UIPercentDrivenInteractiveTransition I override cancel function like that:

      class CustomTransitionManager: UIPercentDrivenInteractiveTransition {
      
          /// Indicates transition direction. Must be set before each transition.
          var forward: Bool = true
      
          /// Current navigation controller used for transition. Must be set before transition starts
          var nc: UINavigationController?
      
          /**
           * Hack #1 with UINavigationController here
           */
          override func cancel() {
              super.cancel()
      
              if forward {
                  self.nc?.setNavigationBarHidden(false, animated: false)
              }
              self.nc?.setNavigationBarHidden(forward, animated: false)
          }
      }
      
    • In each of the view controllers (A, B, C) I make the following hack:

      override func viewWillAppear(_ animated: Bool) {
          super.viewWillAppear(animated)
      
          // Hide and immediately show navigation bar: this will restore it's correct state
          self.navigationController?.setNavigationBarHidden(true, animated: false)
          self.navigationController?.setNavigationBarHidden(false, animated: true)
      }
      

    The best solution probably would be a modal fullscreen presentation of C, but in my case I am working with a project already having corrupted navigation hierarchy and I had no time to fix it properly. Basically, that's the reason why I faced this issue.