141
votes

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.

16

16 Answers

135
votes

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;
 }
94
votes

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
    }
}
85
votes

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
    }
}
34
votes

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.

30
votes

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
        }
    }
}
12
votes

Swift 4

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

Swift 5. Clean and simple.

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

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;
    }
}
3
votes

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
}
3
votes

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.

2
votes

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...

2
votes

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;
    }
}
1
votes
if let navigationController = self.navigationController, navigationController.isBeingPresented {
    // being presented
}else{
    // being pushed
}
0
votes

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

-1
votes

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
        }
    
    }
    
-3
votes
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