ViewController
iOSDevelopment
UIKit
MobileDevelopment
SwiftProgramming

Dismissing a Presented View Controller

Master System Design with Codemia

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

Introduction

When a view controller is presented modally in UIKit, dismissing it is normally straightforward: call dismiss(animated:completion:). The part that confuses many developers is not the method itself, but which controller should call it and how the modal hierarchy affects the result.

The rule of thumb is simple. Dismiss the presented flow from somewhere inside that flow, or from the presenting controller if you still have a clean reference to it.

Dismiss from the Presented Controller

The most common pattern is for the modal screen to dismiss itself after the user taps a close button.

swift
1import UIKit
2
3final class SettingsViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6        view.backgroundColor = .systemBackground
7
8        navigationItem.rightBarButtonItem = UIBarButtonItem(
9            barButtonSystemItem: .close,
10            target: self,
11            action: #selector(closeTapped)
12        )
13    }
14
15    @objc private func closeTapped() {
16        dismiss(animated: true)
17    }
18}

This works because the presented controller can ask UIKit to dismiss the modal presentation it belongs to. In most cases, that is the cleanest approach.

Dismiss from the Presenting Controller

The presenting controller can also dismiss the modal later if it still controls the flow.

swift
1import UIKit
2
3final class HomeViewController: UIViewController {
4    private var shownController: UIViewController?
5
6    func showSettings() {
7        let settings = SettingsViewController()
8        let nav = UINavigationController(rootViewController: settings)
9        shownController = nav
10        present(nav, animated: true)
11    }
12
13    func closeSettingsIfNeeded() {
14        shownController?.dismiss(animated: true)
15    }
16}

That can be useful when an external event should close the modal, such as a login finishing or a timeout expiring.

One common source of confusion is presenting a UINavigationController whose root is your content controller. In that setup, the navigation controller is the presented object, even though the user sees the root screen inside it.

That means dismissing from the root controller still works, but conceptually the whole modal stack is being dismissed.

swift
let editor = EditorViewController()
let nav = UINavigationController(rootViewController: editor)
present(nav, animated: true)

If editor later calls dismiss(animated: true), UIKit dismisses the presented navigation controller and everything inside it.

Use Completion for Follow-Up Work

If something must happen only after the dismissal animation finishes, use the completion closure.

swift
dismiss(animated: true) {
    print("Modal is gone")
}

That is the right place for cleanup that depends on the modal no longer being visible, such as triggering a refresh on the underlying screen.

Understand Dismiss vs Pop

Another frequent confusion is mixing modal dismissal with navigation popping. If a screen was pushed onto a navigation stack, use navigationController?.popViewController(animated: true). If it was presented modally, use dismiss.

The visible UI can look similar, but the ownership model is different:

  • 'dismiss closes a modal presentation'
  • 'popViewController moves back inside a navigation stack'

Knowing how the screen was shown tells you how it should be removed.

Common Pitfalls

The most common mistake is calling popViewController for a modally presented screen, or calling dismiss for a screen that was only pushed.

Another issue is forgetting that a navigation controller may be the presented object, not the root content controller you created first.

It is also easy to retain stale references to a dismissed controller and then try to dismiss it again later. That usually indicates the presentation flow should be simplified.

Finally, when dismissal needs to trigger later work, put that logic in the completion closure instead of assuming the animation has already finished.

Summary

  • Use dismiss(animated:completion:) to close modally presented view controllers.
  • A presented controller can usually dismiss itself cleanly.
  • If the modal is wrapped in a navigation controller, dismissing the root closes the full presented stack.
  • Use popViewController only for navigation-stack pushes, not modal presentations.
  • Put post-dismiss logic in the completion closure when timing matters.

Course illustration
Course illustration

All Rights Reserved.