Error Message
Debugging
Software Development
Programming
Code Troubleshooting

Warning Attempt to present on which is already presenting null

Master System Design with Codemia

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

Introduction

This UIKit warning appears when code tries to present a new view controller while the current presenter is already presenting another controller or is not in a valid presentation state. It is common in async flows, repeated button taps, and lifecycle timing mistakes. The fix is usually to serialize presentation attempts and present from the correct top controller.

Core Sections

What the Warning Means in Practice

A typical warning message says the app attempted to present controller B from controller A while A is already presenting something else. UIKit blocks the second presentation to avoid inconsistent view hierarchy state.

Frequent causes:

  • triggering present twice before first animation finishes
  • calling present from a controller not currently in window hierarchy
  • presenting during viewDidLoad before appearance lifecycle completes
  • competing async callbacks that both attempt navigation

Treat this warning as a state management issue, not as a random UIKit bug.

Present From the Top Most Visible Controller

In complex apps with tabs, navigation stacks, and modals, the safest presenter is the top visible controller. Build a helper and centralize modal presentation calls.

swift
1import UIKit
2
3extension UIApplication {
4    func topViewController(
5        base: UIViewController? = UIApplication.shared
6            .connectedScenes
7            .compactMap { ($0 as? UIWindowScene)?.keyWindow }
8            .first?
9            .rootViewController
10    ) -> UIViewController? {
11        if let nav = base as? UINavigationController {
12            return topViewController(base: nav.visibleViewController)
13        }
14        if let tab = base as? UITabBarController,
15           let selected = tab.selectedViewController {
16            return topViewController(base: selected)
17        }
18        if let presented = base?.presentedViewController {
19            return topViewController(base: presented)
20        }
21        return base
22    }
23}

Using one helper avoids scattered presenter assumptions across view models and coordinators.

Guard Against Duplicate Presentation Calls

Before presenting, verify no modal is already active and optionally debounce repeated taps.

swift
1final class LoginViewController: UIViewController {
2    private var isPresentingModal = false
3
4    func showErrorDialog(message: String) {
5        guard !isPresentingModal else { return }
6        guard presentedViewController == nil else { return }
7
8        isPresentingModal = true
9        let alert = UIAlertController(title: "Login failed", message: message, preferredStyle: .alert)
10        alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in
11            self?.isPresentingModal = false
12        })
13        present(alert, animated: true)
14    }
15}

This pattern prevents duplicated alerts caused by rapid retries or parallel network callbacks.

Coordinate Async Flows and Lifecycle Timing

Presentation triggered from async work should be routed to main thread and checked against current lifecycle state. Presenting during transition phases is fragile.

swift
1func handleAuthResult(_ result: Result<Void, Error>) {
2    DispatchQueue.main.async { [weak self] in
3        guard let self else { return }
4        guard self.viewIfLoaded?.window != nil else { return }
5        guard self.presentedViewController == nil else { return }
6
7        switch result {
8        case .success:
9            let vc = SuccessViewController()
10            self.present(vc, animated: true)
11        case .failure(let error):
12            self.showErrorDialog(message: error.localizedDescription)
13        }
14    }
15}

Checking window attachment prevents presentation from off screen controllers after navigation changes.

Use a Navigation Coordinator for Large Apps

When several modules can trigger modals, coordinator based presentation avoids race conditions. The coordinator owns a serial queue of navigation intents and decides what to show next.

Simple coordinator idea:

  • one presentation entry point
  • one active modal at a time
  • queue or drop duplicate intents by key

This architecture removes modal logic from random callbacks and makes warnings significantly less frequent.

Debugging Checklist

When warning appears in logs:

  1. Add temporary logging before every present call.
  2. Log current controller, presented controller, and thread.
  3. Reproduce with UI test that taps rapidly.
  4. Confirm transitions finish before next presentation.

You can also set symbolic breakpoints on UIViewController presentation methods to capture the second conflicting call site immediately.

Common Pitfalls

  • Presenting from controllers that are no longer visible.
  • Triggering multiple present calls from concurrent async callbacks.
  • Showing modals in early lifecycle methods before appearance.
  • Ignoring presentedViewController state checks.
  • Spreading modal logic across many classes without coordination.

Summary

  • The warning indicates overlapping or invalid modal presentation attempts.
  • Present from the top visible controller and centralize logic.
  • Guard duplicate calls with state checks and debouncing.
  • Synchronize async callbacks with main thread and lifecycle checks.
  • Use coordinator style navigation for complex modal flows.

Course illustration
Course illustration

All Rights Reserved.