Working with Navigation Controllers and Tab Bar Controllers in iOS
Course: iOS App Development Training
Topic: Working with Navigation Controllers and Tab Bar Controllers
Course Link: iOS App Development Training
Demo Link: Register for a Course Demo
Official Website: JustAcademy
1. Introduction to Navigation Controllers and Tab Bar Controllers
Navigation controllers and tab bar controllers are important UIKit components used to organize screens and improve navigation in native iOS applications. They help developers create interfaces that are easy to understand, maintain, and use.
A UINavigationController manages a stack of view controllers. It allows users to move from one screen to another, such as opening a product from a list and returning to the list using a Back button.
A UITabBarController organizes an application into separate sections represented by tabs, usually displayed at the bottom of the screen on iPhone. For example, a shopping app might contain Home, Search, Cart, and Profile tabs.
These controllers can also work together. A tab bar controller can manage multiple navigation controllers, allowing each tab to maintain its own navigation history.
Why Are These Controllers Important?
- They organize large applications into manageable screens and sections.
- They provide familiar navigation patterns that users already understand.
- They reduce the need to manually manage transitions between view controllers.
- They support navigation bars, Back buttons, tab items, and transition animations.
- They make it easier to separate features such as Home, Settings, Search, and Profile.
- They help developers build reusable and maintainable UIKit interfaces.
2. Learning Objectives
After completing this topic, learners should be able to:
- Explain the purpose of navigation controllers and tab bar controllers.
- Understand the navigation stack and root view controller.
- Use push and pop operations to navigate between screens.
- Create and configure a tab bar controller using Swift.
- Customize navigation bar titles, buttons, and tab bar items.
- Combine navigation controllers with a tab bar controller.
- Pass data between view controllers.
- Use delegates to respond to navigation and tab selection events.
- Identify common navigation mistakes and test navigation behavior in Xcode.
3. Understanding UINavigationController
UINavigationController is a container view controller that manages a stack of child view controllers. It displays the view controller at the top of the stack and provides navigation controls, such as a navigation bar and a Back button, when appropriate.
Navigation controllers are especially useful for hierarchical interfaces where users move from general information to more detailed information.
Example of Hierarchical Navigation
- The user opens a Products screen.
- The user selects a product.
- The application pushes the Product Details screen onto the navigation stack.
- The user selects Reviews.
- The application pushes the Reviews screen onto the stack.
- The user taps Back to return to Product Details.
- The user taps Back again to return to Products.
Important Navigation Controller Properties
| Property |
Purpose |
viewControllers |
Contains the view controllers currently managed by the navigation controller. |
topViewController |
Returns the controller at the top of the navigation stack. |
visibleViewController |
Identifies the controller whose view is currently visible, including certain presented content. |
navigationBar |
Provides access to the navigation bar managed by the controller. |
isNavigationBarHidden |
Indicates whether the navigation bar is hidden. |
delegate |
Allows a delegate to respond to navigation events and configure custom transitions. |
4. Understanding the Navigation Stack
The navigation stack is an ordered collection of view controllers managed by a navigation controller. It follows the Last In, First Out (LIFO) principle: the most recently pushed controller is normally the first one removed when the user navigates backward.
The first controller is the root view controller. It usually represents the starting screen of that navigation flow. Additional controllers are pushed onto the stack as the user moves deeper into the application.
Navigation Stack Example
Initial stack:
[HomeViewController]
After opening a product:
[HomeViewController, ProductDetailsViewController]
After opening reviews:
[HomeViewController, ProductDetailsViewController, ReviewsViewController]
After tapping Back:
[HomeViewController, ProductDetailsViewController]
The navigation controller displays the topmost controller. When a controller is popped, the controller beneath it becomes visible again.
Push and Pop Operations
| Operation |
Method |
Effect |
| Push |
pushViewController(_:animated:) |
Adds a controller to the top of the navigation stack. |
| Pop |
popViewController(animated:) |
Removes the top controller and returns to the previous one. |
| Pop to root |
popToRootViewController(animated:) |
Removes controllers above the root controller. |
| Pop to a specific controller |
popToViewController(_:animated:) |
Returns to a particular controller already in the stack. |
| Replace the stack |
setViewControllers(_:animated:) |
Sets a new stack of view controllers. |
5. Creating a Navigation Controller in UIKit
A navigation controller can be created programmatically or configured through a storyboard. In a programmatic application, the root view controller is passed to the navigation controller's initializer.
Step 1: Create the Root View Controller
import UIKit
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Home"
}
}
This controller represents the starting screen. The navigation controller can use its title in the navigation bar.
Step 2: Create the Navigation Controller
let homeVC = HomeViewController()
let navigationController = UINavigationController(
rootViewController: homeVC
)
The rootViewController initializer places the Home screen at the bottom of the navigation stack.
Step 3: Display the Navigation Controller
In a UIKit application that manages its window programmatically, the navigation controller can be assigned as the window's root controller. The following example illustrates the setup pattern inside an appropriate scene configuration.
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else {
return
}
let window = UIWindow(windowScene: windowScene)
let homeVC = HomeViewController()
let navigationController = UINavigationController(
rootViewController: homeVC
)
window.rootViewController = navigationController
self.window = window
window.makeKeyAndVisible()
}
}
Important: Use this approach when the project is configured for programmatic window management. If the application already uses a storyboard or another root-controller setup, configure the navigation controller within that existing architecture instead of creating a duplicate window setup.
6. Navigating Between View Controllers
To open another screen, create or obtain the destination view controller and push it onto the navigation stack. UIKit handles the transition and updates the navigation bar.
Example: Opening a Details Screen
import UIKit
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Home"
let button = UIButton(type: .system)
button.setTitle("Open Details", for: .normal)
button.addTarget(
self,
action: #selector(openDetails),
for: .touchUpInside
)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(
equalTo: view.centerXAnchor
),
button.centerYAnchor.constraint(
equalTo: view.centerYAnchor
)
])
}
@objc private func openDetails() {
let detailsVC = DetailsViewController()
navigationController?.pushViewController(
detailsVC,
animated: true
)
}
}
class DetailsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGroupedBackground
title = "Details"
}
}
When the user taps the button, the Details screen is pushed onto the navigation stack. UIKit displays the destination controller and normally provides a Back button to return to Home.
Returning to the Previous Screen
The Back button provided by a navigation controller automatically pops the top controller. A custom action can also perform this operation:
navigationController?.popViewController(animated: true)
Returning Directly to the Root Screen
navigationController?.popToRootViewController(animated: true)
This operation is useful after completing a workflow, such as finishing a multi-step setup process and returning to the main screen.
7. Customizing the Navigation Bar
The navigation bar presents contextual information and actions for the visible screen. Each view controller can configure its own title and navigation items through its navigationItem property.
Setting a Navigation Title
override func viewDidLoad() {
super.viewDidLoad()
title = "Product Details"
}
Adding a Right Bar Button
override func viewDidLoad() {
super.viewDidLoad()
title = "Profile"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Edit",
style: .plain,
target: self,
action: #selector(editProfile)
)
}
@objc private func editProfile() {
print("Edit profile tapped")
}
The right bar button can perform actions such as Edit, Save, Share, or Add. Choose labels and icons that communicate their purpose clearly.
Navigation Bar Best Practices
- Give each screen a clear and meaningful title.
- Use standard Back navigation instead of creating a conflicting custom Back button.
- Use
navigationItem to configure screen-specific actions.
- Prefer standard system controls and behaviors for accessibility and consistency.
- Avoid directly changing the navigation bar's frame or bounds.
- Test the appearance with different screen sizes and interface styles.
8. Understanding UITabBarController
UITabBarController is a container view controller that organizes an application into separate sections. Each tab is associated with a root view controller, and selecting a tab displays the content for that section.
Tab bars are commonly used when an app has several major sections that users need to access frequently, such as Home, Search, Favorites, and Profile.
Common Tab Bar Components
| Component |
Purpose |
UITabBarController |
Manages the tabs and the currently selected view controller. |
UITabBar |
Displays the tab selection interface. |
UITabBarItem |
Defines the title, image, and related information for a tab. |
viewControllers |
Contains the root view controllers displayed as tabs. |
selectedIndex |
Identifies the currently selected controller by its array index. |
selectedViewController |
Provides access to the currently selected view controller. |
delegate |
Allows the app to respond to tab selection and related events. |
9. Creating a Tab Bar Controller in UIKit
A tab bar controller can be configured by creating its child view controllers, assigning each controller a UITabBarItem, and assigning the controllers to the tab bar controller's viewControllers property.
Step 1: Create Individual Tab View Controllers
import UIKit
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Home"
}
}
class SearchViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGroupedBackground
title = "Search"
}
}
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .secondarySystemBackground
title = "Profile"
}
}
Step 2: Configure the Tab Bar Controller
import UIKit
func makeMainTabBarController() -> UITabBarController {
let tabBarController = UITabBarController()
let homeVC = HomeViewController()
homeVC.tabBarItem = UITabBarItem(
title: "Home",
image: UIImage(systemName: "house"),
selectedImage: UIImage(systemName: "house.fill")
)
let searchVC = SearchViewController()
searchVC.tabBarItem = UITabBarItem(
title: "Search",
image: UIImage(systemName: "magnifyingglass"),
selectedImage: UIImage(systemName: "magnifyingglass")
)
let profileVC = ProfileViewController()
profileVC.tabBarItem = UITabBarItem(
title: "Profile",
image: UIImage(systemName: "person"),
selectedImage: UIImage(systemName: "person.fill")
)
tabBarController.viewControllers = [
homeVC,
searchVC,
profileVC
]
tabBarController.selectedIndex = 0
return tabBarController
}
This example creates three tabs and selects Home by default. The order of the controllers in the array determines their order in the tab bar.
Step 3: Assign the Tab Bar Controller as the Root Controller
let mainTabBarController = makeMainTabBarController()
window.rootViewController = mainTabBarController
Use this root-controller assignment within the application's existing window setup. If the app already uses a storyboard or a configured scene, integrate the tab bar controller into that setup rather than assigning another root controller unnecessarily.
10. Customizing Tab Bar Items
Each tab can display a title and an icon. UIKit provides system images through SF Symbols, which can be used with UIImage(systemName:).
Example of a Tab Bar Item
let favoritesItem = UITabBarItem(
title: "Favorites",
image: UIImage(systemName: "heart"),
selectedImage: UIImage(systemName: "heart.fill")
)
Assign the item to the corresponding view controller:
favoritesVC.tabBarItem = favoritesItem
Tab Bar Design Best Practices
- Use short and recognizable labels.
- Choose icons that clearly communicate the purpose of each section.
- Use consistent icon styles across all tabs.
- Place the most important application sections in the main tab bar.
- Avoid creating too many primary destinations.
- Check that labels and icons remain understandable with accessibility settings enabled.
- Use appropriate system colors and test the interface in light and dark appearances.
On iOS, tab bars are normally displayed at the bottom of the interface. If a tab bar controller has more than five child controllers, UIKit generally displays a More interface for additional items, rather than showing every item simultaneously.
11. Combining Navigation Controllers and Tab Bar Controllers
Many production applications combine both controllers. A tab bar controller manages the main sections, while each tab contains its own navigation controller. This allows users to navigate deeper within a section without losing the other tabs.
Example Application Structure
UITabBarController
|
|-- UINavigationController
| |-- HomeViewController
| |-- ProductDetailsViewController
| |-- ReviewsViewController
|
|-- UINavigationController
| |-- SearchViewController
| |-- SearchResultsViewController
|
|-- UINavigationController
| |-- ProfileViewController
| |-- EditProfileViewController
For example, the Home tab can open Product Details and Reviews. The user can then switch to Search and navigate to search results. When the user returns to Home, its navigation stack can remain available.
Example: A Tab Bar with Separate Navigation Stacks
import UIKit
func makeAppInterface() -> UITabBarController {
let tabBarController = UITabBarController()
let homeVC = HomeViewController()
let homeNavigation = UINavigationController(
rootViewController: homeVC
)
homeNavigation.tabBarItem = UITabBarItem(
title: "Home",
image: UIImage(systemName: "house"),
selectedImage: UIImage(systemName: "house.fill")
)
let searchVC = SearchViewController()
let searchNavigation = UINavigationController(
rootViewController: searchVC
)
searchNavigation.tabBarItem = UITabBarItem(
title: "Search",
image: UIImage(systemName: "magnifyingglass"),
selectedImage: UIImage(systemName: "magnifyingglass")
)
let profileVC = ProfileViewController()
let profileNavigation = UINavigationController(
rootViewController: profileVC
)
profileNavigation.tabBarItem = UITabBarItem(
title: "Profile",
image: UIImage(systemName: "person"),
selectedImage: UIImage(systemName: "person.fill")
)
tabBarController.viewControllers = [
homeNavigation,
searchNavigation,
profileNavigation
]
return tabBarController
}
Explanation: Each tab contains a separate navigation controller. The tab bar controller switches between those navigation controllers, while each navigation controller manages the screens within its own section.
Important: A view controller should not be assigned as a child to multiple container controllers simultaneously. Create the appropriate controller hierarchy and use the tab bar controller as the outer container when combining these components.
12. Passing Data Between View Controllers
Navigation often requires sending information from one screen to another. For example, a product list may pass a selected product identifier or model to the Product Details screen.
Example: Passing a Product Name
import UIKit
class ProductDetailsViewController: UIViewController {
var productName: String = "Unknown Product"
override func viewDidLoad() {
super.viewDidLoad()
title = productName
view.backgroundColor = .systemBackground
}
}
class ProductListViewController: UIViewController {
func showProductDetails() {
let detailsVC = ProductDetailsViewController()
detailsVC.productName = "Wireless Headphones"
navigationController?.pushViewController(
detailsVC,
animated: true
)
}
}
The source controller creates the destination, assigns the required value, and then pushes the destination onto the navigation stack. The destination uses the passed value when configuring its interface.
Other Data-Passing Approaches
- Initializer injection: Pass required data through a custom initializer.
- Delegate pattern: Send information or actions from a child controller back to another controller.
- Closures: Provide a callback that the destination invokes when an action occurs.
- Shared model or service: Use a shared data source for information needed by multiple screens.
- NotificationCenter: Broadcast events when multiple independent components need to respond.
Prefer clear ownership and explicit data flow. Avoid relying on global mutable variables to pass every piece of information between screens.
13. Understanding Navigation and Tab Bar Delegates
Delegates allow an application to respond to user interactions and lifecycle events associated with container controllers.
UINavigationControllerDelegate
The navigation controller delegate can be used to observe transitions between view controllers and provide custom transition animations.
import UIKit
class NavigationObserver: NSObject, UINavigationControllerDelegate {
func navigationController(
_ navigationController: UINavigationController,
didShow viewController: UIViewController,
animated: Bool
) {
print("Displayed screen: \(String(describing: type(of: viewController)))")
}
}
Assign the delegate to the navigation controller when appropriate:
let observer = NavigationObserver()
navigationController.delegate = observer
In a real application, the delegate object must remain strongly retained for as long as it is needed. Also remember that assigning a delegate replaces any previously assigned delegate, so coordinate delegate responsibilities in larger projects.
UITabBarControllerDelegate
The tab bar controller delegate can observe tab selection or control whether a tab can be selected.
import UIKit
class TabSelectionObserver: NSObject, UITabBarControllerDelegate {
func tabBarController(
_ tabBarController: UITabBarController,
didSelect viewController: UIViewController
) {
print("A tab was selected")
}
}
Assign the delegate after creating the tab bar controller:
let tabObserver = TabSelectionObserver()
tabBarController.delegate = tabObserver
As with the navigation delegate, retain the observer and avoid replacing an existing delegate unintentionally.
14. Storyboard-Based Navigation Setup
Xcode storyboards provide a visual way to configure navigation and tab bar controllers. They can be useful for smaller UIKit projects or projects that already use storyboard-based interfaces.
Creating a Navigation Controller in a Storyboard
- Open the storyboard in Xcode.
- Select the starting view controller.
- Choose Editor and then Embed In, followed by Navigation Controller.
- Xcode creates a navigation controller and connects it to the original controller as the root view controller.
- Set the screen title through the controller's navigation item or code.
- Use a Show segue or programmatic push to navigate to another screen.
Creating a Tab Bar Controller in a Storyboard
- Open the storyboard and add a Tab Bar Controller.
- Connect the required view controllers to the tab bar controller using relationship segues.
- Configure the tab bar item's title and image for each child controller.
- Set the appropriate initial controller for the storyboard or scene configuration.
- Run the app and verify that each tab opens the correct section.
When an app requires both tab-based sections and hierarchical navigation, embed each tab's root view controller in a separate navigation controller and connect those navigation controllers to the tab bar controller.
15. Navigation in SwiftUI: NavigationStack and TabView
Although UINavigationController and UITabBarController are UIKit components, SwiftUI offers declarative equivalents for common navigation patterns.
NavigationStack supports hierarchical navigation and a navigation path.
NavigationLink provides a control for navigating to another view.
TabView organizes content into selectable tabs.
Example: NavigationStack
import SwiftUI
struct ProductListView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("Wireless Headphones") {
ProductDetailView(
productName: "Wireless Headphones"
)
}
NavigationLink("Smart Watch") {
ProductDetailView(
productName: "Smart Watch"
)
}
}
.navigationTitle("Products")
}
}
}
struct ProductDetailView: View {
let productName: String
var body: some View {
VStack {
Text(productName)
.font(.title)
Text("Product information appears here.")
}
.padding()
.navigationTitle("Details")
}
}
Example: TabView
import SwiftUI
struct MainTabView: View {
var body: some View {
TabView {
NavigationStack {
Text("Home Screen")
.navigationTitle("Home")
}
.tabItem {
Label("Home", systemImage: "house")
}
NavigationStack {
Text("Search Screen")
.navigationTitle("Search")
}
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
NavigationStack {
Text("Profile Screen")
.navigationTitle("Profile")
}
.tabItem {
Label("Profile", systemImage: "person")
}
}
}
}
This example gives each tab its own NavigationStack. It demonstrates the common pattern of combining tab-based navigation with hierarchical navigation in SwiftUI.
16. Common Mistakes and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Back button does not appear. |
The view controller is not inside a navigation controller or is the root controller. |
Verify the navigation controller hierarchy and current stack. |
| Push navigation does nothing. |
navigationController is nil or the action is not being called. |
Check the controller hierarchy, button target, and action method. |
| Tab bar icons do not appear. |
The image is invalid or the tab bar item is not configured correctly. |
Verify SF Symbol names and assign the item to the correct controller. |
| Wrong screen opens after selecting a tab. |
The view controllers were added to the tab bar in the wrong order. |
Review the viewControllers array and tab item configuration. |
| Navigation history disappears when switching tabs. |
The tabs do not each have their own navigation controller or the stack is being reset. |
Use a separate navigation controller for each tab when independent histories are required. |
| Data is missing on the destination screen. |
The value was not assigned before navigation. |
Pass required data before pushing or presenting the destination. |
| Delegate callbacks are not called. |
The delegate was not assigned or the delegate object was deallocated. |
Assign the delegate and retain it for the required lifetime. |
| Tab content is hidden behind a bar. |
The layout does not respect safe areas or container layout. |
Use safe-area-aware constraints and standard container behavior. |
17. Best Practices for Navigation Architecture
- Use a navigation controller for hierarchical flows such as lists, details, and editing screens.
- Use a tab bar controller for a small set of major application sections.
- Give each tab a separate navigation controller when users should maintain independent navigation histories.
- Keep view controllers focused on presenting and coordinating their own screen.
- Pass data explicitly rather than depending on unrelated controllers to share mutable state.
- Use standard system navigation behavior wherever possible.
- Provide meaningful titles, tab labels, and accessibility information.
- Avoid creating excessive layers of nested navigation containers.
- Test interactive Back gestures, tab switching, and deep navigation flows.
- Keep the navigation structure consistent with the user's mental model of the application.
18. Hands-On Exercises
- Create a UIKit project with a Home screen and a Details screen.
- Embed Home in a navigation controller and open Details using a button.
- Add a custom navigation bar button to the Details screen.
- Create a tab bar controller with Home, Search, and Profile tabs.
- Assign an SF Symbol and title to every tab.
- Embed each tab's root controller in a separate navigation controller.
- Pass a product name from a list screen to a detail screen.
- Add a navigation controller delegate and log when a screen appears.
- Add a tab bar controller delegate and log when a tab is selected.
- Re-create the same navigation pattern using SwiftUI's
NavigationStack and TabView.
19. Mini Project: Shopping Application Navigation
Project Objective: Build a basic shopping application interface that combines a tab bar controller with independent navigation stacks.
Project Requirements
- Create Home, Search, Cart, and Profile sections.
- Give each tab its own navigation controller.
- Display a list of sample products on the Home screen.
- Open a Product Details screen when a product is selected.
- Pass the selected product's name and price to the details screen.
- Provide a Back button to return to the product list.
- Allow users to switch tabs without unnecessarily resetting each tab's navigation stack.
- Add a profile-editing screen within the Profile tab.
- Use clear tab labels, system icons, and meaningful screen titles.
Expected Outcome
The application should provide intuitive navigation between major sections while allowing users to move deeper into each section. Switching tabs should not unexpectedly erase the navigation history of other tabs.
20. Interview Questions and Answers
Q1. What is a UINavigationController?
Answer: It is a UIKit container view controller that manages a stack of view controllers and supports hierarchical navigation through push and pop operations.
Q2. What is a navigation stack?
Answer: It is the ordered collection of view controllers managed by a navigation controller. The topmost controller is normally the currently displayed screen.
Q3. What is the difference between push and pop?
Answer: Push adds a view controller to the navigation stack, while pop removes the top controller and returns to the previous screen.
Q4. What is a UITabBarController?
Answer: It is a UIKit container controller that manages several top-level sections and displays the content associated with the selected tab.
Q5. Can a tab contain a navigation controller?
Answer: Yes. Each tab can use a navigation controller as its root controller, allowing independent navigation within each section.
Q6. What is UITabBarItem?
Answer: It represents a tab's displayed information, such as its title and image.
Q7. How do you navigate back to the root controller?
Answer: Call popToRootViewController(animated:) on the navigation controller.
Q8. How can data be passed between view controllers?
Answer: Data can be passed through properties or initializers, or communicated using delegates, closures, or shared models, depending on the design.
Q9. What is the purpose of UINavigationControllerDelegate?
Answer: It allows a delegate to respond to navigation transitions and support custom transition behavior.
Q10. What is the difference between UIKit navigation and SwiftUI navigation?
Answer: UIKit commonly uses UINavigationController and UITabBarController, while SwiftUI uses declarative components such as NavigationStack, NavigationLink, and TabView.
21. Quick Revision Checklist
- Understand the purpose of
UINavigationController and UITabBarController.
- Know how the navigation stack works.
- Understand push, pop, pop to root, and stack replacement.
- Know how to create and configure tab bar items.
- Understand how to combine a tab bar controller with separate navigation controllers.
- Learn how to pass data between view controllers.
- Understand navigation and tab bar delegate responsibilities.
- Know the equivalent SwiftUI patterns using
NavigationStack and TabView.
- Test Back navigation, tab selection, and independent navigation history.
22. Conclusion
Navigation controllers and tab bar controllers are fundamental tools for organizing UIKit applications. A navigation controller manages hierarchical movement through a stack of screens, while a tab bar controller provides quick access to the application's major sections.
Combining a tab bar controller with a separate navigation controller for each tab provides a flexible architecture for applications with multiple independent workflows. Developers should also understand data passing, delegate behavior, navigation bar customization, and the SwiftUI equivalents to build intuitive and maintainable iOS interfaces.
23. Additional Learning Resources