iOS Development
Navigation Stack
ViewController Management
Swift Programming
App Development

Removing viewcontrollers from navigation stack

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

In UIKit, a UINavigationController stores a stack of view controllers that represent the user's navigation history. Removing controllers from that stack is possible, but you need to be explicit about whether you are popping the top controller, returning to a known controller, or replacing the stack entirely. The right approach depends on the user flow you want to preserve.

Use Pop Methods for Normal Back Navigation

If you only need to remove the current top controller, use popViewController. If you want to go all the way back to the root, use popToRootViewController.

swift
if let navigationController = navigationController {
    navigationController.popViewController(animated: true)
}
swift
navigationController?.popToRootViewController(animated: true)

These are the safest options because they follow normal navigation semantics. Use them whenever the user is simply moving backward.

Remove Specific Controllers by Replacing the Stack

If you want to remove one controller from the middle of the stack, you cannot do that with a single built-in "remove" call. Instead, read the current viewControllers array, filter it, and assign the new array back.

swift
1if let navigationController = navigationController {
2    let filtered = navigationController.viewControllers.filter {
3        !($0 is LoginViewController)
4    }
5    navigationController.setViewControllers(filtered, animated: false)
6}

This is common after authentication or onboarding. For example, once the user has logged in successfully, you may want to remove the login screen so tapping Back does not return to it.

Because this replaces the stack directly, it should be used deliberately. You are editing the navigation history, not just moving through it.

Build the Desired Final Stack Explicitly

For complex flows, it is often cleaner to build the exact stack you want and set it once.

swift
1let home = HomeViewController()
2let details = DetailsViewController(itemID: 42)
3
4navigationController?.setViewControllers([home, details], animated: true)

This is useful when you have finished a wizard, restored app state, or handled a deep link. Instead of pushing and popping several controllers, define the final state directly.

It also makes the intended navigation flow easier to understand during code review.

Be Careful With Lifecycle Assumptions

When you edit the viewControllers array directly, do not assume it behaves exactly like a series of animated pops. The navigation controller updates its stack, but controllers removed from the middle are not going through a standard back-navigation interaction.

That matters if your code relies on lifecycle callbacks to clean up resources or save state. If a screen needs explicit teardown logic, call that from a well-defined place in your coordinator or controller flow instead of depending only on navigation side effects.

A Coordinator Makes Stack Changes Safer

If your app has nontrivial flows, a coordinator or router object is usually a better place for stack manipulation than individual view controllers. That keeps navigation policy centralized.

swift
1final class AppCoordinator {
2    let navigationController: UINavigationController
3
4    init(navigationController: UINavigationController) {
5        self.navigationController = navigationController
6    }
7
8    func finishOnboarding() {
9        let home = HomeViewController()
10        navigationController.setViewControllers([home], animated: true)
11    }
12}

This approach helps avoid scattered code where every screen edits the stack differently.

Common Pitfalls

  • Using direct stack replacement when a simple popViewController would be clearer.
  • Removing screens from the middle of the stack without thinking through the resulting Back behavior.
  • Editing the navigation stack inside multiple view controllers instead of centralizing the flow.
  • Assuming direct stack mutation behaves exactly like animated pops for lifecycle handling.
  • Forgetting that a removed login or onboarding controller cannot be reached again through normal back navigation.

Summary

  • Use popViewController and popToRootViewController for normal backward navigation.
  • To remove specific controllers, read viewControllers, filter or rebuild it, and call setViewControllers.
  • Direct stack replacement is appropriate for login, onboarding, restore-state, and deep-link flows.
  • Treat navigation-stack edits as flow-control decisions, not small UI tweaks.
  • Centralizing navigation logic in a coordinator makes these operations easier to maintain.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.