Fix: Navigation Bar Stays Hidden on Subsequent Screens in Swift

Fix: Navigation Bar Stays Hidden on Subsequent Screens in Swift

Written by

in

Quick answer: To hide the navigation bar on only one screen, toggle visibility in both viewWillAppear and viewWillDisappear using navigationController?.setNavigationBarHidden(_:animated:). Hiding the bar in viewDidLoad alters the shared container and leaves subsequent screens without a navigation bar.

Why this happens

A UINavigationController manages a hierarchy of child view controllers, but it owns only one shared instance of UINavigationBar. When you set isNavigationBarHidden = true or call setNavigationBarHidden(_:animated:), you are modifying the navigation controller itself, not an isolated property of your current view controller.

Setting self.navigationController?.isNavigationBarHidden = true inside viewDidLoad() causes two distinct issues. First, viewDidLoad() runs only once when the controller is initially instantiated. If a user pushes a second controller and then pops back to the first, viewDidLoad() will not fire again, leaving the bar in whatever state the second screen defined. Second, if you push another view controller onto the stack without explicitly restoring the bar, the container preserves its hidden state on all subsequent screens.

Failing to pass the animated flag during transitions also breaks interactive pop gestures. If an un-animated change occurs while the user drags from the screen edge to go back, the navigation bar can disappear abruptly or leave an empty space at the top of the interface.

How to fix it

1. Toggle visibility in lifecycle methods with animation

The standard approach in UIKit is pairing visibility updates inside viewWillAppear(_:) and viewWillDisappear(_:). Always pass the incoming animated argument to setNavigationBarHidden(_:animated:) so the navigation bar fades smoothly in coordination with the push or pop transition.

Add this to the view controller that needs a hidden navigation bar:

import UIKit

class RootViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(true, animated: animated)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        navigationController?.setNavigationBarHidden(false, animated: animated)
    }
}

When RootViewController appears, it hides the navigation bar. When the user navigates to any detail screen, viewWillDisappear runs and restores the bar for the incoming view controller.

2. Centralize bar visibility with UINavigationControllerDelegate

If you have a complex navigation flow with multiple screens that alternate between hidden and visible states, placing toggle logic inside every individual controller can become brittle. A canceled swipe-to-pop gesture can occasionally cause lifecycle timing desynchronizations.

You can handle navigation bar visibility centrally by adopting UINavigationControllerDelegate:

import UIKit

class MainNavigationController: UINavigationController, UINavigationControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }

    func navigationController(
        _ navigationController: UINavigationController,
        willShow viewController: UIViewController,
        animated: Bool
    ) {
        // Determine visibility based on controller type or protocol conformance
        let shouldHideBar = viewController is RootViewController
        navigationController.setNavigationBarHidden(shouldHideBar, animated: animated)
    }
}

This approach checks the destination controller immediately before the transition runs. It prevents code duplication across view controllers and ensures that the bar visibility correctly reflects the top view controller even after canceled gestures.

3. Handling SwiftUI views inside a UIHostingController

If your screen is built in SwiftUI and presented inside a UIKit UINavigationController via UIHostingController, configure toolbar visibility directly in the SwiftUI view body rather than toggling UIKit properties in a host wrapper:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("No Navigation Bar Here")
        }
        .toolbar(.hidden, for: .navigationBar)
    }
}

SwiftUI automatically manages the presentation transition when pushing to another SwiftUI view where the navigation bar is visible.

How to prevent it

  • Never alter isNavigationBarHidden inside viewDidLoad(); reserve screen-level appearance changes for viewWillAppear(_:).
  • Always use setNavigationBarHidden(_:animated:) and pass the animated parameter from the lifecycle callback.
  • Pair every hide call in viewWillAppear with a corresponding show call in viewWillDisappear on the same view controller.
  • For navigation flows containing multiple hidden and shown states, adopt UINavigationControllerDelegate to manage visibility globally.

FAQ

Why does the navigation bar snap abruptly during a swipe-to-pop gesture?

This happens when you modify isNavigationBarHidden directly or pass animated: false to setNavigationBarHidden. Always pass the animated parameter provided by viewWillAppear or viewWillDisappear so UIKit coordinates the alpha animation with the gesture progress.

What happens if a user starts an interactive pop and cancels it?

When using setNavigationBarHidden(_:animated:) inside viewWillAppear and viewWillDisappear, UIKit automatically handles reversed transition animations if the interactive pop is canceled. If you experience visual desynchronization, move visibility control to navigationController(_:willShow:animated:) in a UINavigationControllerDelegate.

Should I hide the navigation bar if I only want to remove the back button?

No. If you only want to prevent navigation backwards or remove the default arrow, set navigationItem.setHidesBackButton(true, animated: false) instead of hiding the entire navigation bar.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.