iOS
UIViewController
RootViewController
Swift
NavigationController

How do I get the RootViewController from a pushed controller?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

From a pushed controller, the root of the current navigation flow is usually the first controller in navigationController?.viewControllers. The catch is that this is the root of the navigation stack, not necessarily the application's outermost root view controller. That distinction matters in apps that use tab bars, scenes, modal containers, or multiple navigation stacks.

The Normal Case

From a pushed controller, the root of the current navigation stack is:

swift
if let root = navigationController?.viewControllers.first as? RootViewController {
    print("Found root controller: \\(root)")
}

This works because viewControllers is the ordered stack managed by the navigation controller, and the first element is the root. If you only need the controller without casting, navigationController?.viewControllers.first is enough.

Many bugs come from calling both of these concepts "the root controller." They are not the same thing. In a tab-based app, the window root might be a UITabBarController, while the current navigation stack root is a screen inside the selected tab.

swift
let navRoot = navigationController?.viewControllers.first
let windowRoot = view.window?.rootViewController

Use the navigation root when you want the base screen of the current push flow. Use the window root only when you actually need the outermost container.

Example With a Pushed Stack

Suppose the stack is HomeViewController, then DetailsViewController, then SettingsViewController. If code inside SettingsViewController needs the first controller in that stack, this is the normal pattern:

swift
if let home = navigationController?.viewControllers.first as? HomeViewController {
    home.refreshData()
}

That code is correct for a push flow. It is not a general solution for every controller hierarchy in iOS.

If the current controller was presented modally, navigationController may be nil or may refer to a navigation controller embedded inside the modal itself. In those cases, presentingViewController or a container-specific reference may be more relevant than viewControllers.first.

swift
if let presenter = presentingViewController {
    print("Presented by: \\(presenter)")
}

This is why the wording of the original problem matters. "From a pushed controller" points to the navigation stack. "From any current controller in the app" is a different and much harder question.

Prefer Communication Patterns

Even though you can reach back to the stack root directly, doing so too often creates tight coupling. A pushed controller that constantly manipulates the root controller usually becomes harder to test and harder to reuse.

swift
protocol SettingsViewControllerDelegate: AnyObject {
    func settingsDidChange()
}

With that pattern, the pushed controller does not need to know whether the receiver is the root controller, a coordinator, or something else.

A Small Helper

If you need this often, wrap it in a helper:

swift
1extension UIViewController {
2    func rootInNavigationStack<T: UIViewController>(as type: T.Type) -> T? {
3        navigationController?.viewControllers.first as? T
4    }
5}

That keeps the call sites short and makes the intent obvious.

Common Pitfalls

  • Calling navigationController?.viewControllers.first from a controller that was presented modally. Fix: confirm the controller is actually inside the navigation stack you want.
  • Confusing the stack root with view.window?.rootViewController. Fix: decide whether you need the current navigation root or the outer app container.
  • Force-casting the result with as!. Fix: use safe casts because stack composition changes over time.
  • Reaching back to the root controller for business logic from deep screens. Fix: prefer delegates, coordinators, or shared state when the communication is architectural rather than navigational.

Summary

  • From a pushed controller, the usual answer is navigationController?.viewControllers.first.
  • That value is the root of the current navigation stack, not necessarily the app window root.
  • Modal and container-based flows may require different references.
  • Safe casting is better than force-casting when retrieving a specific root controller type.
  • Direct access works, but delegate or coordinator patterns are often cleaner long-term.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

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

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.