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:
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
:
Property | Type | Description |
---|---|---|
viewControllers | [UIViewController] | The stack of view controllers managed by the navigation controller. |
topViewController | UIViewController? | The currently visible view controller (top of the stack). |
visibleViewController | UIViewController? | The currently visible view controller, including presented controllers. |
navigationBar | UINavigationBar | The navigation bar displayed at the top. |
isNavigationBarHidden | Bool | Whether the navigation bar is hidden. |
toolbar | UIToolbar? | The optional toolbar at the bottom. |
isToolbarHidden | Bool | Whether the toolbar is hidden. |
delegate | UINavigationControllerDelegate? | Delegate for handling navigation events. |
Example: Configuring Properties:
navigationController.isNavigationBarHidden = false
navigationController.navigationBar.prefersLargeTitles = true
navigationController.toolbar.isHidden = true
Key Methods of UINavigationController
UINavigationController
provides methods to manage the navigation stack:
Method | Description |
---|---|
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:
let newViewController = UIViewController()
navigationController.pushViewController(newViewController, animated: true)
UINavigationControllerDelegate Methods
The UINavigationControllerDelegate
protocol provides methods to handle navigation events:
Method | Description |
---|---|
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:
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:
// 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:
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:
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:
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
orrightBarButtonItem
for single buttons. - Use
leftBarButtonItems
orrightBarButtonItems
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:
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:
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.swiftsaveButton.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.swiftnavigationController.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
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
}
}