ViewController
iOS Development
Modal Presentation
Swift Programming
UIKit

Is it possible to determine whether ViewController is presented as Modal?

Master System Design with Codemia

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

Introduction

There is no single isModal property on UIViewController in UIKit. However, you can determine if a view controller was presented modally by checking several properties: presentingViewController is non-nil when presented modally, isBeingPresented is true during the presentation transition, and the view controller is not part of a navigation stack push. The most reliable approach combines multiple checks because navigation controllers can themselves be presented modally.

Basic Check: presentingViewController

swift
1extension UIViewController {
2    var isModal: Bool {
3        if presentingViewController != nil {
4            return true
5        }
6        if navigationController?.presentingViewController?.presentedViewController == navigationController {
7            return true
8        }
9        if tabBarController?.presentingViewController is UITabBarController {
10            return true
11        }
12        return false
13    }
14}
15
16// Usage
17override func viewDidAppear(_ animated: Bool) {
18    super.viewDidAppear(animated)
19    if isModal {
20        print("Presented modally")
21    } else {
22        print("Pushed or embedded")
23    }
24}

This checks three scenarios: direct modal presentation, modal navigation controller, and modal tab bar controller.

Checking During Presentation Lifecycle

swift
1override func viewWillAppear(_ animated: Bool) {
2    super.viewWillAppear(animated)
3
4    if isBeingPresented {
5        print("Being presented modally right now")
6        // Add a dismiss button
7        navigationItem.leftBarButtonItem = UIBarButtonItem(
8            barButtonSystemItem: .close,
9            target: self,
10            action: #selector(dismissModal)
11        )
12    }
13
14    if isMovingToParent {
15        print("Being pushed onto a navigation stack")
16    }
17}
18
19@objc func dismissModal() {
20    dismiss(animated: true)
21}

isBeingPresented is true only during the view controller's initial modal presentation transition. isMovingToParent is true when being pushed onto a navigation controller.

More Robust Check

swift
1extension UIViewController {
2    var isModallyPresented: Bool {
3        // Check if directly presented
4        if presentingViewController != nil {
5            return true
6        }
7
8        // Check if the navigation controller is presented modally
9        if let nav = navigationController,
10           nav.presentingViewController?.presentedViewController == nav,
11           nav.viewControllers.first == self {
12            return true
13        }
14
15        // Check the presentation style
16        if modalPresentationStyle == .formSheet ||
17           modalPresentationStyle == .pageSheet ||
18           modalPresentationStyle == .fullScreen {
19            // This alone is not sufficient — also need presentingViewController
20        }
21
22        return false
23    }
24}

Determining Presentation in SwiftUI

swift
1import SwiftUI
2
3struct DetailView: View {
4    @Environment(\.dismiss) var dismiss
5    @Environment(\.isPresented) var isPresented
6
7    var body: some View {
8        VStack {
9            Text("Detail View")
10            if isPresented.wrappedValue {
11                Button("Dismiss") { dismiss() }
12            }
13        }
14    }
15}
16
17// Presented as sheet (modal)
18.sheet(isPresented: $showDetail) {
19    DetailView()
20}
21
22// Pushed via NavigationLink (not modal)
23NavigationLink("Detail") {
24    DetailView()
25}

In SwiftUI, @Environment(\.isPresented) indicates whether the view is inside a modal presentation context.

Adding a Close Button Conditionally

swift
1class DetailViewController: UIViewController {
2
3    override func viewDidLoad() {
4        super.viewDidLoad()
5        title = "Details"
6
7        // Show close button only when presented modally
8        if isModal {
9            navigationItem.leftBarButtonItem = UIBarButtonItem(
10                barButtonSystemItem: .close,
11                target: self,
12                action: #selector(closeButtonTapped)
13            )
14        }
15    }
16
17    @objc private func closeButtonTapped() {
18        dismiss(animated: true)
19    }
20}
21
22// Present modally — close button appears
23let detailVC = DetailViewController()
24let nav = UINavigationController(rootViewController: detailVC)
25present(nav, animated: true)
26
27// Push — no close button
28navigationController?.pushViewController(DetailViewController(), animated: true)

Checking modalPresentationStyle

swift
1override func viewWillAppear(_ animated: Bool) {
2    super.viewWillAppear(animated)
3
4    // Check the actual presentation style after presentation
5    switch presentationController?.presentationStyle {
6    case .fullScreen:
7        print("Full screen modal")
8    case .pageSheet:
9        print("Page sheet modal")
10    case .formSheet:
11        print("Form sheet modal")
12    case .popover:
13        print("Popover")
14    default:
15        print("Other or pushed")
16    }
17}

Common Pitfalls

  • Checking presentingViewController too early: In viewDidLoad, presentingViewController may not yet be set. Check in viewWillAppear or viewDidAppear for reliable results.
  • Forgetting that navigation controllers can be modally presented: When a UINavigationController is presented modally and your view controller is its root, self.presentingViewController is nil — the navigation controller is the presented one. Check navigationController?.presentingViewController as well.
  • Assuming isBeingPresented persists after presentation: isBeingPresented is only true during the presentation transition (in viewWillAppear/viewDidAppear). Checking it later always returns false. Store the value in a property if needed after the transition completes.
  • Relying on modalPresentationStyle before presentation: The modalPresentationStyle property reflects what was set on the view controller, not necessarily the actual presentation style used. The system may adapt the style (e.g., on iPad). Check presentationController?.adaptivePresentationStyle for the actual style.
  • Not handling iOS 13+ card-style modals: Since iOS 13, the default modal presentation is .pageSheet (a card), not .fullScreen. Users can swipe down to dismiss without calling your dismiss logic. Implement UIAdaptivePresentationControllerDelegate to handle interactive dismissal.

Summary

  • Check presentingViewController != nil as the primary indicator of modal presentation
  • Also check navigationController?.presentingViewController for modally-presented navigation controllers
  • Use isBeingPresented in viewWillAppear/viewDidAppear for transition-time checks only
  • Conditionally show a close/dismiss button based on the isModal check
  • In SwiftUI, use @Environment(\.isPresented) to determine modal context
  • Handle iOS 13+ card-style dismissal by implementing UIAdaptivePresentationControllerDelegate

Course illustration
Course illustration

All Rights Reserved.