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

131,357

Solution 1

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

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

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

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

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
        }
    }
}
Share:
131,357
meaning-matters
Author by

meaning-matters

Should software be simple, usable, easy to understand, ...? I believe there's one unifying force that matters most: meaning. When focussing on what is most meaningful for users, properties like simplicity and usability will follow naturally. This principle can be applied to all levels: For example, from arranging code statements, to presenting data. It's my guide that has led to great results for already more than two decades. Cornelis

Updated on July 08, 2022

Comments

  • meaning-matters
    meaning-matters almost 2 years

    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.

  • ColdLogic
    ColdLogic almost 10 years
    Can still be in a modal navigation controller
  • JRG-Developer
    JRG-Developer almost 10 years
    +1: Nice approach. :D This assumes, of course, that you care that presentingViewController's presentedViewController must match self (and not simply be an ancestor).
  • meaning-matters
    meaning-matters almost 10 years
    I found this in another SO post. But, does not work if the pushed view controller's parent is a modal; which is the situation I'm having.
  • ColdLogic
    ColdLogic almost 10 years
    added if([self presentingViewController]) return YES;
  • ColdLogic
    ColdLogic almost 10 years
    If presentingViewController is not nil, then either you or an ancestor is being presented modally.
  • meaning-matters
    meaning-matters almost 10 years
    As I wrote, presentingViewController is always YES in my case; does not help.
  • ColdLogic
    ColdLogic almost 10 years
    What do you mean it doesn't work then? Is it returning yes when you expect it to return no?
  • meaning-matters
    meaning-matters almost 10 years
    I tried this too before posting, and it does not work, isBeingPresented is NO. But I see the reason now, I'm embedding my presented view controller in a UINavigationController, and that's the one I'm pushing.
  • A . Radej
    A . Radej almost 10 years
    So 'modal' and 'pushed on navigation stack' are not mutually exclusive. Thinking this depends on the context, but checking if self.navigationController is not nil does answer whether it's a view controller of a navigation controller.
  • meaning-matters
    meaning-matters almost 10 years
    See edits, I was embedding my view controller in a navigation controller, so all these methods were not working. Since you were the first with a in principle good answer you won all the points.
  • rmaddy
    rmaddy almost 10 years
    You can't push a navigation controller. Perhaps you meant that you are presenting the navigation controller.
  • rmaddy
    rmaddy almost 10 years
    @Daniel The difference is between "pushed" and "presented". "Modal' has nothing to do with it. I believe "ColdLogic" meant "presented" when they said "modal".
  • Yevhen Dubinin
    Yevhen Dubinin almost 10 years
    presentingViewController returns YES for pushed VC, when there is a UITabBarController being set as a root. So, does not suitable in my case.
  • jowie
    jowie about 9 years
    For some reason, self.isBeingPresented is always nil for me. I've had to use self.presentingViewController. I have no idea why.
  • Admin
    Admin almost 9 years
    @jowie: Please be aware of the method definition, it returns a BOOL, not an NSObject => - (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);
  • jowie
    jowie almost 9 years
    @SebastianKeller yes I know, but po self.isBeingPresented shouldn't return nil. If I test on other projects and different view controllers, the result of that statement is false. My project is iOS 8.1 and above.
  • rmaddy
    rmaddy almost 9 years
    @jowie Use p, not po when printing a primitive value. po is for printing objects.
  • Morkrom
    Morkrom almost 9 years
    This property is deprecated.
  • funct7
    funct7 almost 9 years
    Documentation for isBeingPresented - This method returns YES only when called from inside the viewWillAppear: and viewDidAppear: methods.
  • Lee
    Lee over 8 years
    This does not work if you present a view controller then it pushes another one.
  • meaning-matters
    meaning-matters almost 8 years
    Yes, I meant that I present the UINavigationController.
  • Terrence
    Terrence over 7 years
    @BridgeTheGap What documentation are you referring to? I don't see this in the UIViewController Overview nor on the property reference itself.
  • rmaddy
    rmaddy over 7 years
    @Terrence It seems the latest documentation doesn't show that information but it used to be there. The isBeingPresented, isBeingDismissed, isMovingFromParentViewController and isMovingToParentViewController are only valid inside the 4 view[Will|Did][Disa|A]ppear methods.
  • Alexander Abakumov
    Alexander Abakumov about 7 years
    which you should always do anyway - please explain why?
  • E-Riddie
    E-Riddie over 6 years
    Well in general when you present modally, you put the viewController on a navigationController and you present it. If that is the case your statement would be wrong, however on the code this case is handled. Please improve your answer :)
  • Colin Swelin
    Colin Swelin over 6 years
    "This does not work if you present a view controller then it pushes another one" That's not the intention of this, the pushed view controller isn't being presented.
  • nickdnk
    nickdnk about 6 years
    Alexander, you shouldn't, really.
  • jk7
    jk7 almost 6 years
    isBeingPresented works in other situations but seems to give inconsistent results. Ex. After presenting a popover from within the UITextFieldDelegate method, textField:shouldChangeCharactersInRange:replacementString:, isBeingPresented correctly returned YES while still within the scope of that method call, but the next time that method was called it return NO even though the popover was still being presented.
  • malinois
    malinois over 5 years
    Should be better in a var, like var isModal: Bool {}
  • Eli Burke
    Eli Burke over 5 years
    Swift 4.2 / iOS 12. Still works well, but be aware that navigationController?.presentingViewController?.presentedVie‌​wController === navigationController will evaluate to true if both are nil (for example, if you call it on a view controller that has not yet been presented).
  • YannSteph
    YannSteph over 5 years
    @malinois is changed
  • Cesare
    Cesare over 5 years
    Upvoted but didn't work on my end. I'm presenting a view controller modally but it returns false. Not sure why.
  • gklka
    gklka about 5 years
    This method does not work when you use same view controller class in multiple places, since it does check only the class of it. You can explicitly check the equality instead.
  • damjandd
    damjandd about 5 years
    What does the last false parameter in the return statement do?
  • Jean Raymond Daher
    Jean Raymond Daher almost 5 years
    good job that deals with all the use cases. room for a bit of refactoring probably but still upvote !!
  • Hlung
    Hlung about 4 years
    Why do you need to check tabBarController?.presentingViewController is UITabBarController ? Does it matter if that presentingViewController is also a UITabBarController?
  • Hlung
    Hlung about 4 years
    And if navigationController is nil, isModal will return true. Is this intended?
  • famfamfam
    famfamfam almost 4 years
    you need change to let presentingIsNavigation = navigationController?.presentingViewController?.presentedVie‌​wController == navigationController && navigationController != nil
  • Tà Truhoada
    Tà Truhoada almost 3 years
    Swift 5: presentingIsNavigation = true if navigationController is nil
  • meaning-matters
    meaning-matters over 2 years
    Thanks for this answer. I'll try to find time to check this. BTW, I think it's semantically better to put the second return statement in an else { } block because it's the opposite case of having a navigation controller.