How to check if a view controller is presented modally or pushed on a navigation stack?

IosObjective CSwiftUiviewcontrollerUinavigationcontroller

Ios Problem Overview


How can I, in my view controller code, differentiate between:

  • presented modally
  • pushed on navigation stack

Both presentingViewController and isMovingToParentViewController are YES in both cases, so are not very helpful.

What complicates things is that my parent view controller is sometimes modal, on which the to be checked view controller is pushed.

It turns out my issue is that I embed my HtmlViewController in a UINavigationController which is then presented. That's why my own attempts and the good answers below were not working.

HtmlViewController*     termsViewController = [[HtmlViewController alloc] initWithDictionary:dictionary];
UINavigationController* modalViewController;

modalViewController = [[UINavigationController alloc] initWithRootViewController:termsViewController];
modalViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:modalViewController
                   animated:YES
                 completion:nil];

I guess I'd better tell my view controller when it's modal, instead of trying to determine.

Ios Solutions


Solution 1 - Ios

Take with a grain of salt, didn't test.

- (BOOL)isModal {
     if([self presentingViewController])
         return YES;
     if([[[self navigationController] presentingViewController] presentedViewController] == [self navigationController])
         return YES;
     if([[[self tabBarController] presentingViewController] isKindOfClass:[UITabBarController class]])
         return YES;
     
    return NO;
 }

Solution 2 - Ios

In Swift:

Add a flag to test if it's a modal by the class type:

// MARK: - UIViewController implementation

extension UIViewController {
        
    var isModal: Bool {
        
        let presentingIsModal = presentingViewController != nil
        let presentingIsNavigation = navigationController?.presentingViewController?.presentedViewController == navigationController
        let presentingIsTabBar = tabBarController?.presentingViewController is UITabBarController
        
        return presentingIsModal || presentingIsNavigation || presentingIsTabBar
    }
}

Solution 3 - Ios

You overlooked one method: isBeingPresented.

isBeingPresented is true when the view controller is being presented and false when being pushed.

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    if ([self isBeingPresented]) {
        // being presented
    } else if ([self isMovingToParentViewController]) {
        // being pushed
    } else {
        // simply showing again because another VC was dismissed
    }
}

Solution 4 - Ios

Swift 5
Here is solution that addresses the issue mentioned with previous answers, when isModal() returns true if pushed UIViewController is in a presented UINavigationController stack.

extension UIViewController {
    var isModal: Bool {
        if let index = navigationController?.viewControllers.firstIndex(of: self), index > 0 {
            return false
        } else if presentingViewController != nil {
            return true
        } else if navigationController?.presentingViewController?.presentedViewController == navigationController {
            return true
        } else if tabBarController?.presentingViewController is UITabBarController {
            return true
        } else {
            return false
        }
    }
}

It does work for me so far. If some optimizations, please share.

Solution 5 - Ios

> self.navigationController != nil would mean it's in a navigation > stack.

In order to handle the case that the current view controller is pushed while the navigation controller is presented modally, I have added some lines of code to check if the current view controller is the root controller in the navigation stack .

extension UIViewController {
    var isModal: Bool {
        if let index = navigationController?.viewControllers.firstIndex(of: self), index > 0 {
            return false
        } else if presentingViewController != nil {
            return true
        } else if let navigationController = navigationController, navigationController.presentingViewController?.presentedViewController == navigationController {
            return true
        } else if let tabBarController = tabBarController, tabBarController.presentingViewController is UITabBarController {
            return true
        } else {
            return false
        }
    }
}

Solution 6 - Ios

Swift 5. Clean and simple.

if navigationController?.presentingViewController != nil {
    // Navigation controller is being presented modally
}

Solution 7 - Ios

Swift 4

var isModal: Bool {
    return presentingViewController != nil ||
           navigationController?.presentingViewController?.presentedViewController === navigationController ||
           tabBarController?.presentingViewController is UITabBarController
}

Solution 8 - Ios

Swift 5
This handy extension handles few more cases than previous answers. These cases are VC(view controller) is the root VC of app window, VC is added as child to parent VC. It tries to return true only if the viewcontroller is modally presented.

extension UIViewController {
    /**
      returns true only if the viewcontroller is presented.
    */
    var isModal: Bool {
        if let index = navigationController?.viewControllers.firstIndex(of: self), index > 0 {
            return false
        } else if presentingViewController != nil {
            if let parent = parent, !(parent is UINavigationController || parent is UITabBarController) {
                return false
            }
            return true
        } else if let navController = navigationController, navController.presentingViewController?.presentedViewController == navController {
            return true
        } else if tabBarController?.presentingViewController is UITabBarController {
            return true
        }
        return false
    }
}

Thanks to Jonauz's answer. Again there is space for more optimizations. Please discuss about case that need to be handled in comment section.

Solution 9 - Ios

As many folks here suggest, that "checking" methods don't work well for all cases, in my project I've come up with solution to manage that manually. The point is, we usually manage presentation on our own - this is not what happens behind the scene and we must to introspect.

DEViewController.h file:

#import <UIKit/UIKit.h>

// it is a base class for all view controllers within a project
@interface DEViewController : UIViewController 

// specify a way viewcontroller, is presented  by another viewcontroller
// the presented view controller should manually assign the value to it
typedef NS_ENUM(NSUInteger, SSViewControllerPresentationMethod) {
    SSViewControllerPresentationMethodUnspecified = 0,
    SSViewControllerPresentationMethodPush,
    SSViewControllerPresentationMethodModal,
};
@property (nonatomic) SSViewControllerPresentationMethod viewControllerPresentationMethod;

// other properties/methods...
@end

The presentations now could be managed this way:

pushed on navigation stack:

// DETestViewController inherits from DEViewController
DETestViewController *vc = [DETestViewController new];
vc.viewControllerPresentationMethod = SSViewControllerPresentationMethodPush;
[self.navigationController pushViewController:vc animated:YES];

presented modally with navigation:

DETestViewController *vc = [DETestViewController new];
vc.viewControllerPresentationMethod = SSViewControllerPresentationMethodModal;
UINavigationController *nav = [[UINavigationController alloc]
                               initWithRootViewController:vc];
[self presentViewController:nav animated:YES completion:nil];

presented modally:

DETestViewController *vc = [DETestViewController new];
vc.viewControllerPresentationMethod = SSViewControllerPresentationMethodModal;
[self presentViewController:vc animated:YES completion:nil];

Also, in DEViewController we could add a fallback to "checking" if the aforementioned property equals to SSViewControllerPresentationMethodUnspecified:

- (BOOL)isViewControllerPushed
{
    if (self.viewControllerPresentationMethod != SSViewControllerPresentationMethodUnspecified) {
        return (BOOL)(self.viewControllerPresentationMethod == SSViewControllerPresentationMethodPush);
    }
    
    else {
        // fallback to default determination method
        return (BOOL)self.navigationController.viewControllers.count > 1;
    }
}

Solution 10 - Ios

Assuming that all viewControllers that you present modally are wrapped inside a new navigationController (which you should always do anyway), you can add this property to your VC.

private var wasPushed: Bool {
    guard let vc = navigationController?.viewControllers.first where vc == self else {
        return true
    }

    return false
}

Solution 11 - Ios

If you are using ios 5.0 or later than please use this code

-(BOOL)isPresented
{
    if ([self isBeingPresented]) {
        // being presented
         return YES;
    } else if ([self isMovingToParentViewController]) {
        // being pushed
         return NO;
    } else {
        // simply showing again because another VC was dismissed
         return NO;
    }
}

Solution 12 - Ios

To detect your controller is pushed or not just use below code in anywhere you want:

if ([[[self.parentViewController childViewControllers] firstObject] isKindOfClass:[self class]]) {

    // Not pushed
}
else {

    // Pushed
}

I hope this code can help anyone...

Solution 13 - Ios

if let navigationController = self.navigationController, navigationController.isBeingPresented {
    // being presented
}else{
    // being pushed
}

Solution 14 - Ios

self.navigationController != nil would mean it's in a navigation stack.

Solution 15 - Ios

What about this solution - tested under iOS 15 and Xcode 13.1:

var isPresented: Bool {
    if let nvc = navigationController {
        return nvc.viewControllers.firstIndex(of: self) == 0
    } else {
        return presentingViewController != nil
    }
}

Solution 16 - Ios

For some one who's wondering, How to tell ViewController that it is being presented

if A is presenting/pushing B

  1. Define an enum and property in B

    enum ViewPresentationStyle {
        case Push
        case Present
    }
    
    //and write property 
    
    var vcPresentationStyle : ViewPresentationStyle = .Push //default value, considering that B is pushed 
    
  2. Now in A view controller, tell B if it is being presented/pushed by assigning presentationStyle

    func presentBViewController() {
        let bViewController = B()
        bViewController.vcPresentationStyle = .Present //telling B that it is being presented
        self.presentViewController(bViewController, animated: true, completion: nil)
    }
    
  3. Usage in B View Controller

    override func viewDidLoad() {
        super.viewDidLoad()
        
        if self.vcPresentationStyle == .Present {
            //is being presented 
        }
        else {
            //is being pushed
        }
                       
    }
    

Solution 17 - Ios

id presentedController = self.navigationController.modalViewController;
if (presentedController) {
     // Some view is Presented
} else {
     // Some view is Pushed
}

This will let you know if viewController is presented or pushed

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionmeaning-mattersView Question on Stackoverflow
Solution 1 - IosColdLogicView Answer on Stackoverflow
Solution 2 - IosKing-WizardView Answer on Stackoverflow
Solution 3 - IosrmaddyView Answer on Stackoverflow
Solution 4 - IosJonauzView Answer on Stackoverflow
Solution 5 - IosJibeexView Answer on Stackoverflow
Solution 6 - IosKirill KudaevView Answer on Stackoverflow
Solution 7 - IosCharlton ProvatasView Answer on Stackoverflow
Solution 8 - IosMehedi HasanView Answer on Stackoverflow
Solution 9 - IosYevhen DubininView Answer on Stackoverflow
Solution 10 - IosSenõr GansoView Answer on Stackoverflow
Solution 11 - IosShahbaz AbbasiView Answer on Stackoverflow
Solution 12 - IosArash ZeinoddiniView Answer on Stackoverflow
Solution 13 - IosmktoView Answer on Stackoverflow
Solution 14 - IosDanielView Answer on Stackoverflow
Solution 15 - IosblackjacxView Answer on Stackoverflow
Solution 16 - IosSaifView Answer on Stackoverflow
Solution 17 - IosiCoder86View Answer on Stackoverflow