iOS Development
View Controllers
User Interface
App Design
Mobile Development

Warning -Presenting view controllers on detached view controllers is discouraged

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The warning "Presenting view controllers on detached view controllers is discouraged" appears when you call present(_:animated:completion:) on a view controller that is not part of the active view controller hierarchy. A view controller is "detached" when it has been removed from its parent, dismissed, or not yet added to the window's root hierarchy. The fix is to ensure you always present from the topmost visible view controller — typically the root view controller or the currently presented controller.

What Causes the Warning

swift
1class ViewController: UIViewController {
2    override func viewDidLoad() {
3        super.viewDidLoad()
4
5        // This may trigger the warning if called before
6        // the view controller is fully in the hierarchy
7        let alert = UIAlertController(title: "Hello", message: "World", preferredStyle: .alert)
8        alert.addAction(UIAlertAction(title: "OK", style: .default))
9        present(alert, animated: true)  // Warning if self is detached
10    }
11}

Calling present() in viewDidLoad() is risky because the view controller may not yet be added to the window hierarchy. The view is loaded but the controller may not be "attached" to the display chain.

Fix 1: Present in viewDidAppear

swift
1class ViewController: UIViewController {
2    override func viewDidAppear(_ animated: Bool) {
3        super.viewDidAppear(animated)
4
5        // Safe — the view controller is fully in the hierarchy
6        let alert = UIAlertController(title: "Welcome", message: "You are logged in", preferredStyle: .alert)
7        alert.addAction(UIAlertAction(title: "OK", style: .default))
8        present(alert, animated: true)
9    }
10}

viewDidAppear is called after the view controller is visible and part of the active hierarchy. This is the safest lifecycle method for presenting other view controllers.

Fix 2: Present from the Top View Controller

swift
1extension UIViewController {
2    static var topMost: UIViewController? {
3        var topController = UIApplication.shared.connectedScenes
4            .compactMap { ($0 as? UIWindowScene)?.keyWindow?.rootViewController }
5            .first
6
7        while let presented = topController?.presentedViewController {
8            topController = presented
9        }
10
11        // Handle navigation and tab controllers
12        if let nav = topController as? UINavigationController {
13            topController = nav.visibleViewController
14        }
15        if let tab = topController as? UITabBarController {
16            topController = tab.selectedViewController
17        }
18
19        return topController
20    }
21}
22
23// Usage from anywhere
24UIViewController.topMost?.present(alertController, animated: true)

This utility finds the topmost visible view controller by traversing the presentation chain from the root. It handles navigation controllers, tab controllers, and modally presented controllers.

Fix 3: Present from the Root View Controller

swift
1// Simple approach — present from the window's root
2func presentFromRoot(_ viewController: UIViewController, animated: Bool = true) {
3    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
4          let rootVC = windowScene.windows.first?.rootViewController else {
5        return
6    }
7
8    // Dismiss any existing presentation first
9    if rootVC.presentedViewController != nil {
10        rootVC.dismiss(animated: false) {
11            rootVC.present(viewController, animated: animated)
12        }
13    } else {
14        rootVC.present(viewController, animated: animated)
15    }
16}

Presenting from the root view controller works when you do not have a deep presentation chain. If another controller is already presented, dismiss it first.

Common Scenario: Presenting After Dismiss

swift
1class ParentVC: UIViewController {
2    func showFirst() {
3        let firstVC = FirstViewController()
4        firstVC.onDismiss = { [weak self] in
5            // BAD: presenting immediately after dismiss — firstVC may still be detached
6            let secondVC = SecondViewController()
7            self?.present(secondVC, animated: true)
8        }
9        present(firstVC, animated: true)
10    }
11}
12
13class FirstViewController: UIViewController {
14    var onDismiss: (() -> Void)?
15
16    func done() {
17        dismiss(animated: true) { [weak self] in
18            // GOOD: present in the dismiss completion handler
19            self?.onDismiss?()
20        }
21    }
22}

Always present the next view controller in the dismiss completion handler. Presenting before the dismissal animation completes causes the detached warning because the presenting controller is still mid-transition.

Common Scenario: Presenting from a Child View Controller

swift
1// BAD: child view controller presenting directly
2class ChildVC: UIViewController {
3    func showAlert() {
4        let alert = UIAlertController(title: "Error", message: "Something failed", preferredStyle: .alert)
5        alert.addAction(UIAlertAction(title: "OK", style: .default))
6        // This may warn if ChildVC is embedded but not the presenting controller
7        present(alert, animated: true)
8    }
9}
10
11// GOOD: delegate to the parent or use the top view controller
12class ChildVC: UIViewController {
13    func showAlert() {
14        let alert = UIAlertController(title: "Error", message: "Something failed", preferredStyle: .alert)
15        alert.addAction(UIAlertAction(title: "OK", style: .default))
16
17        // Present from the nearest non-detached ancestor
18        let presenter = parent ?? self
19        presenter.present(alert, animated: true)
20    }
21}

Child view controllers added via addChild() are in the hierarchy but may not be the right presenter. Use parent to delegate presentation upward.

Debugging the Issue

swift
1// Check if a view controller is in the hierarchy
2func isInHierarchy(_ vc: UIViewController) -> Bool {
3    return vc.viewIfLoaded?.window != nil
4}
5
6// Log the view controller chain
7func printHierarchy() {
8    var vc = UIApplication.shared.connectedScenes
9        .compactMap { ($0 as? UIWindowScene)?.keyWindow?.rootViewController }
10        .first
11
12    var depth = 0
13    while let current = vc {
14        print("\(String(repeating: "  ", count: depth))\(type(of: current))")
15        if let nav = current as? UINavigationController {
16            print("\(String(repeating: "  ", count: depth + 1))Stack: \(nav.viewControllers.map { type(of: $0) })")
17        }
18        vc = current.presentedViewController
19        depth += 1
20    }
21}

Check viewIfLoaded?.window != nil to verify a view controller is attached to a window. If it is nil, the controller is detached and should not present other controllers.

Common Pitfalls

  • Presenting in viewDidLoad or viewWillAppear: The view controller may not be fully in the hierarchy during these lifecycle methods. Wait until viewDidAppear to present other controllers.
  • Not using the dismiss completion handler: Calling present() immediately after dismiss() without waiting for the completion block causes the warning. Always present in the completion closure of dismiss.
  • Presenting from a view controller that was popped from a navigation stack: After popViewController, the popped controller is detached. Any delayed callbacks (network responses, timers) that try to present from it trigger the warning.
  • Multiple scenes on iPad: On iPad with multiple scenes, UIApplication.shared.keyWindow is deprecated and may return the wrong window. Use UIWindowScene to find the correct window for the active scene.
  • Ignoring the warning: While the presentation may still appear to work, detached presentations can cause visual glitches, broken transitions, and crashes on certain iOS versions. Always fix the warning.

Summary

  • The warning means you are presenting from a view controller not in the active hierarchy
  • Present in viewDidAppear, not viewDidLoad or viewWillAppear
  • Use a topMost view controller utility to find the correct presenter
  • Always present the next controller in the dismiss completion handler
  • Check viewIfLoaded?.window != nil to verify a controller is attached
  • Delegate presentation upward using parent when presenting from child view controllers

Course illustration
Course illustration

All Rights Reserved.