Skip to content

UINavigationController

UINavigationController is a UIKit class that manages a stack-based navigation interface, allowing users to navigate through a hierarchy of view controllers. It is a subclass of UIViewController and provides a navigation bar and optional toolbar for navigating between screens. This document covers the key properties, methods, and usage of UINavigationController, including a section on adding buttons to the navigation bar, along with examples and best practices.

Overview of UINavigationController

UINavigationController manages a stack of view controllers, where each view controller represents a screen in the navigation hierarchy. It provides a navigation bar at the top with a title, optional back button, and customizable buttons. Users can push new view controllers onto the stack to navigate forward and pop them to go back, with smooth animations provided by default.

Creating a UINavigationController

You can create a UINavigationController programmatically or via Interface Builder (storyboards/xibs). It requires a root view controller to initialize.

Programmatic Example:

swift
import UIKit

let rootViewController = UIViewController()
let navigationController = UINavigationController(rootViewController: rootViewController)
window?.rootViewController = navigationController

Key Properties of UINavigationController

Below are the most commonly used properties of UINavigationController:

PropertyTypeDescription
viewControllers[UIViewController]The stack of view controllers managed by the navigation controller.
topViewControllerUIViewController?The currently visible view controller (top of the stack).
visibleViewControllerUIViewController?The currently visible view controller, including presented controllers.
navigationBarUINavigationBarThe navigation bar displayed at the top.
isNavigationBarHiddenBoolWhether the navigation bar is hidden.
toolbarUIToolbar?The optional toolbar at the bottom.
isToolbarHiddenBoolWhether the toolbar is hidden.
delegateUINavigationControllerDelegate?Delegate for handling navigation events.

Example: Configuring Properties:

swift
navigationController.isNavigationBarHidden = false
navigationController.navigationBar.prefersLargeTitles = true
navigationController.toolbar.isHidden = true

Key Methods of UINavigationController

UINavigationController provides methods to manage the navigation stack:

MethodDescription
pushViewController(_:animated:)Pushes a new view controller onto the stack.
popViewController(animated:)Pops the top view controller and returns it.
popToViewController(_:animated:)Pops view controllers until the specified one is at the top.
popToRootViewController(animated:)Pops all view controllers back to the root.
setViewControllers(_:animated:)Replaces the entire stack with a new array of view controllers.
setNavigationBarHidden(_:animated:)Hides or shows the navigation bar with animation.
setToolbarHidden(_:animated:)Hides or shows the toolbar with animation.

Example: Navigating:

swift
let newViewController = UIViewController()
navigationController.pushViewController(newViewController, animated: true)

UINavigationControllerDelegate Methods

The UINavigationControllerDelegate protocol provides methods to handle navigation events:

MethodDescription
navigationController(_:willShow:animated:)Called before a view controller is shown.
navigationController(_:didShow:animated:)Called after a view controller is shown.
navigationController(_:animationControllerFor:from:to:)Provides a custom animation controller for transitions.

Example: Implementing Delegate:

swift
navigationController.delegate = self

extension ViewController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        print("Showing view controller: \(viewController)")
    }
}

Adding Buttons to the Navigation Bar

The navigation bar can display buttons on the left or right side of a view controller’s navigationItem. These buttons are typically UIBarButtonItem instances and can trigger actions, such as saving data, opening a menu, or navigating.

Creating Bar Button Items

You can create UIBarButtonItem instances with text, images, or system items (e.g., .add, .done).

Example: Adding Buttons:

swift
// Right bar button
let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped))
navigationItem.rightBarButtonItem = saveButton

// Left bar button
let menuButton = UIBarButtonItem(title: "Menu", style: .plain, target: self, action: #selector(menuTapped))
navigationItem.leftBarButtonItem = menuButton

@objc func saveTapped() {
    print("Save button tapped")
}

@objc func menuTapped() {
    print("Menu button tapped")
}

Using Multiple Buttons

To add multiple buttons on one side, use an array of UIBarButtonItem.

Example:

swift
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped))
let editButton = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editTapped))
navigationItem.rightBarButtonItems = [addButton, editButton]

Custom Views as Buttons

You can use a custom view (e.g., UIButton) as a bar button item.

Example:

swift
let customButton = UIButton(type: .system)
customButton.setTitle("Custom", for: .normal)
customButton.addTarget(self, action: #selector(customTapped), for: .touchUpInside)
let customBarButton = UIBarButtonItem(customView: customButton)
navigationItem.rightBarButtonItem = customBarButton

Adding a UIMenu (iOS 13+)

Attach a UIMenu to a bar button item for a dropdown menu.

Example:

swift
let action1 = UIAction(title: "Copy", image: UIImage(systemName: "doc.on.doc")) { _ in
    print("Copy selected")
}
let action2 = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { _ in
    print("Share selected")
}
let menu = UIMenu(title: "Options", children: [action1, action2])
let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu)
navigationItem.rightBarButtonItem = menuButton

Notes:

  • Use navigationItem.leftBarButtonItem or rightBarButtonItem for single buttons.
  • Use leftBarButtonItems or rightBarButtonItems for multiple buttons.
  • Ensure buttons have clear titles or recognizable icons (e.g., SF Symbols).
  • Test button accessibility with VoiceOver.

Customizing the Navigation Bar

Appearance

Customize the navigation bar’s appearance using UINavigationBar properties.

Example:

swift
navigationController.navigationBar.prefersLargeTitles = true
navigationController.navigationBar.tintColor = .systemBlue
navigationController.navigationBar.barTintColor = .systemGray6

Title and Prompt

Set the title and optional prompt for each view controller’s navigationItem.

Example:

swift
navigationItem.title = "Home"
navigationItem.prompt = "Select an option"

Best Practices

  • Use Clear Navigation: Ensure the navigation hierarchy is intuitive and the back button clearly indicates the previous screen.

  • Support Accessibility: Set accessibilityLabel for bar button items.

    swift
    saveButton.accessibilityLabel = "Save changes"
  • Use Auto Layout: Combine with SnapKit or constraints for subviews in view controllers.

  • Handle Pop Gestures: Support the interactive pop gesture (interactivePopGestureRecognizer) but disable it if needed.

    swift
    navigationController.interactivePopGestureRecognizer?.isEnabled = false
  • Test Navigation: Verify push/pop transitions and button actions across devices and orientations.

  • Customize Animations: Use UINavigationControllerDelegate for custom transition animations.

Troubleshooting

  • Navigation Bar Not Showing: Ensure isNavigationBarHidden = false and the view controller is part of the navigation stack.
  • Buttons Not Responding: Verify the target-action is correctly set and the selector exists.
  • Layout Issues: Check safe area insets and contentInsetAdjustmentBehavior for content alignment.
  • Back Button Issues: Customize the back button using navigationItem.backBarButtonItem if needed.

Example: Complete UINavigationController Setup

swift
import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        // Configure navigation bar
        navigationItem.title = "Main Screen"
        navigationController?.navigationBar.prefersLargeTitles = true
        
        // Add right bar button with menu
        let action1 = UIAction(title: "Copy", image: UIImage(systemName: "doc.on.doc")) { _ in
            print("Copy selected")
        }
        let action2 = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { _ in
            print("Share selected")
        }
        let menu = UIMenu(title: "Options", children: [action1, action2])
        let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu)
        menuButton.accessibilityLabel = "Options menu"
        navigationItem.rightBarButtonItem = menuButton
        
        // Add left bar button
        let backButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(backTapped))
        navigationItem.leftBarButtonItem = backButton
        
        // Add a button to push a new view controller
        let pushButton = UIButton(type: .system)
        pushButton.setTitle("Go to Next Screen", for: .normal)
        pushButton.addTarget(self, action: #selector(pushNext), for: .touchUpInside)
        view.addSubview(pushButton)
        
        // Auto Layout with SnapKit
        pushButton.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }
    }
    
    @objc func backTapped() {
        print("Back button tapped")
    }
    
    @objc func pushNext() {
        let nextVC = UIViewController()
        nextVC.view.backgroundColor = .systemGray6
        nextVC.navigationItem.title = "Next Screen"
        navigationController?.pushViewController(nextVC, animated: true)
    }
}

// App setup
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        let rootVC = ViewController()
        let navController = UINavigationController(rootViewController: rootVC)
        window?.rootViewController = navController
        window?.makeKeyAndVisible()
        return true
    }
}

Resources

Released under the MIT License.