- Published on
> Finding the Top View Controller in Swift
- Authors

- Name
- Mick MacCallum
- @0x7fs
Sometimes you need to present a view controller from code that doesn't have direct access to the current view hierarchy—a networking layer showing an auth prompt, an analytics handler displaying an alert, or a deep link handler pushing a new screen. Finding the "top" view controller requires traversing through presented controllers, navigation stacks, and tab bars to locate the one currently visible to the user.
The Traversal Logic
The top view controller isn't simply the root view controller of your window. It's whatever controller is currently on screen, which might be several layers deep: a tab bar containing a navigation controller that presented a modal which pushed another view controller. You need to walk this chain to find the end.
extension UIWindow {
@MainActor
func topViewController() -> UIViewController? {
var current = rootViewController
while let next = current?.presentedViewController ??
(current as? UINavigationController)?.visibleViewController ??
(current as? UITabBarController)?.selectedViewController {
current = next
}
return current
}
}
This walks the hierarchy by checking three possibilities at each step: if there's a presented modal, follow it; if the current controller is a navigation controller, get its visible controller; if it's a tab bar, get the selected tab. When none of these apply, you've found the top.
Getting the Window
To use this, you need a reference to the window. In modern iOS with scenes:
@MainActor
func getTopViewController() -> UIViewController? {
guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }),
let window = windowScene.windows.first(where: { $0.isKeyWindow }) else {
return nil
}
return window.topViewController()
}
Or as a convenience extension on UIApplication:
extension UIApplication {
@MainActor
var topViewController: UIViewController? {
guard let windowScene = connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }),
let window = windowScene.windows.first(where: { $0.isKeyWindow }) else {
return nil
}
return window.topViewController()
}
}
// Usage
if let top = UIApplication.shared.topViewController {
top.present(alertController, animated: true)
}
A More Explicit Implementation
If you prefer clarity over conciseness, here's the same logic written out:
extension UIWindow {
@MainActor
func topViewController() -> UIViewController? {
var top = rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
return top
}
}
This version makes the priority order explicit: presented view controllers take precedence, then navigation controller contents, then tab bar selection.
Handling Custom Container Controllers
If your app uses custom container view controllers, the standard traversal won't know how to find their visible child. Extend the logic to handle your containers:
extension UIWindow {
@MainActor
func topViewController() -> UIViewController? {
var current = rootViewController
while true {
if let presented = current?.presentedViewController {
current = presented
} else if let nav = current as? UINavigationController {
current = nav.visibleViewController
} else if let tab = current as? UITabBarController {
current = tab.selectedViewController
} else if let splitView = current as? UISplitViewController {
current = splitView.viewControllers.last
} else if let pageView = current as? UIPageViewController {
current = pageView.viewControllers?.first
} else {
break
}
}
return current
}
}
Add cases for any custom container types your app defines.
When to Use This
Finding the top view controller is useful for presenting alerts, handling deep links, or responding to push notifications when you're in code that doesn't naturally have view controller context. However, use this sparingly. If you find yourself reaching for the top view controller frequently, consider whether your architecture could be improved—perhaps with a coordinator pattern or a dedicated presentation service that already has the right context.
The @MainActor annotation ensures this code runs on the main thread, which is required since UIApplication and view controllers must only be accessed from the main thread.
// Continue_Learning
Scheduling Alarms with AlarmKit
Learn how to use AlarmKit to schedule alarms that appear in the system Clock app, giving your app native alarm integration on iOS 26.
Reading Your App's Age Rating with StoreKit's ageRatingCode
Learn how to use AppStore.ageRatingCode to read your app's current age rating and react to rating changes for parental consent compliance.
Getting Current Disk Usage in Swift
How to check available and total disk space on iOS devices using URL resource values.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.