158

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.

craft
  • 2,017
  • 1
  • 21
  • 30
meaning-matters
  • 21,929
  • 10
  • 82
  • 142

17 Answers17

138

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;
 }
Cœur
  • 37,241
  • 25
  • 195
  • 267
ColdLogic
  • 7,206
  • 1
  • 28
  • 46
  • +1: Nice approach. :D This assumes, of course, that you care that `presentingViewController`'s `presentedViewController` must match `self` (and not simply be an ancestor). – JRG-Developer May 12 '14 at 23:11
  • 14
    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. – meaning-matters May 12 '14 at 23:12
  • added `if([self presentingViewController]) return YES;` – ColdLogic May 12 '14 at 23:14
  • If `presentingViewController` is not nil, then either you or an ancestor is being presented modally. – ColdLogic May 12 '14 at 23:15
  • 2
    As I wrote, `presentingViewController` is always `YES` in my case; does not help. – meaning-matters May 12 '14 at 23:17
  • What do you mean it doesn't work then? Is it returning yes when you expect it to return no? – ColdLogic May 12 '14 at 23:18
  • 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. – meaning-matters May 12 '14 at 23:28
  • 4
    `presentingViewController` returns `YES` for pushed VC, when there is a `UITabBarController` being set as a root. So, does not suitable in my case. – Yevhen Dubinin Jun 11 '14 at 12:23
  • 6
    This does not work if you present a view controller then it pushes another one. – Lee Nov 12 '15 at 13:58
  • 3
    "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. – Colin Swelin Oct 20 '17 at 14:33
110

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
    }
}
craft
  • 2,017
  • 1
  • 21
  • 30
King-Wizard
  • 15,628
  • 6
  • 82
  • 76
93

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
    }
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • 3
    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. – meaning-matters May 12 '14 at 23:26
  • 1
    You can't push a navigation controller. Perhaps you meant that you are presenting the navigation controller. – rmaddy May 12 '14 at 23:29
  • For some reason, `self.isBeingPresented` is always `nil` for me. I've had to use `self.presentingViewController`. I have no idea why. – jowie Apr 22 '15 at 13:37
  • @jowie: Please be aware of the method definition, it returns a `BOOL`, not an `NSObject` => `- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);` –  Jun 11 '15 at 13:37
  • @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. – jowie Jun 11 '15 at 13:47
  • 3
    @jowie Use `p`, not `po` when printing a primitive value. `po` is for printing objects. – rmaddy Jun 11 '15 at 14:04
  • 49
    Documentation for `isBeingPresented` - This method returns YES only when called from inside the viewWillAppear: and viewDidAppear: methods. – funct7 Jun 25 '15 at 00:30
  • Yes, I meant that I present the `UINavigationController`. – meaning-matters Jul 18 '16 at 07:28
  • @BridgeTheGap What documentation are you referring to? I don't see this in the UIViewController Overview nor on the property reference itself. – Terrence Jan 27 '17 at 18:40
  • 5
    @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. – rmaddy Jan 27 '17 at 18:44
  • `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. – jk7 Jul 17 '18 at 17:16
  • Upvoted but didn't work on my end. I'm presenting a view controller modally but it returns false. Not sure why. – Cesare Dec 26 '18 at 21:32
45

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.

Andrey Gordeev
  • 30,606
  • 13
  • 135
  • 162
Jonauz
  • 4,133
  • 1
  • 28
  • 22
  • Why do you need to check `tabBarController?.presentingViewController is UITabBarController `? Does it matter if that `presentingViewController` is also a UITabBarController? – Hlung Apr 21 '20 at 07:49
  • 1
    And if navigationController is nil, `isModal` will return `true`. Is this intended? – Hlung Apr 21 '20 at 07:58
35

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
        }
    }
}
nikans
  • 2,468
  • 1
  • 28
  • 35
Jibeex
  • 5,509
  • 3
  • 27
  • 37
  • 2
    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 :) – E-Riddie Aug 29 '17 at 12:56
  • good job that deals with all the use cases. room for a bit of refactoring probably but still upvote !! – Jean Raymond Daher Jul 23 '19 at 16:41
21

Swift 5. Clean and simple.

if navigationController?.presentingViewController != nil {
    // Navigation controller is being presented modally
}
teradyl
  • 2,584
  • 1
  • 25
  • 34
Kirill
  • 738
  • 10
  • 26
12

Swift 4

var isModal: Bool {
    return presentingViewController != nil ||
           navigationController?.presentingViewController?.presentedViewController === navigationController ||
           tabBarController?.presentingViewController is UITabBarController
}
Charlton Provatas
  • 2,184
  • 25
  • 18
  • Swift 4.2 / iOS 12. Still works well, but be aware that navigationController?.presentingViewController?.presentedViewController === 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). – Eli Burke Oct 12 '18 at 17:57
9

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.

Mehedi Hasan
  • 329
  • 2
  • 7
  • doesn't work for swiftui cases. There is parent UIHostingControll or event somthing else if swiftui view is presented without navigation view via UIViewContorllerRepresentable – Michał Ziobro Dec 13 '22 at 15:17
4

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
}
Senõr Ganso
  • 1,694
  • 16
  • 23
3

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;
    }
}
Yevhen Dubinin
  • 4,657
  • 3
  • 34
  • 57
2

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;
    }
}
Yuchen
  • 30,852
  • 26
  • 164
  • 234
2

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

Arash Zeinoddini
  • 801
  • 13
  • 19
  • 1
    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. – gklka Feb 13 '19 at 12:39
1
if let navigationController = self.navigationController, navigationController.isBeingPresented {
    // being presented
}else{
    // being pushed
}
mkto
  • 4,584
  • 5
  • 41
  • 65
0

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
        }
    
    }
    
Saif
  • 2,678
  • 2
  • 22
  • 38
0

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
    }
}
blackjacx
  • 9,011
  • 7
  • 45
  • 56
  • 1
    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. – meaning-matters Nov 10 '21 at 09:31
-1

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

JRG-Developer
  • 12,454
  • 8
  • 55
  • 81
Daniel
  • 8,794
  • 4
  • 48
  • 71
  • 27
    Can still be in a modal navigation controller – ColdLogic May 12 '14 at 23:05
  • 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. – Daniel May 12 '14 at 23:27
  • @Daniel The difference is between "pushed" and "presented". "Modal' has nothing to do with it. I believe "ColdLogic" meant "presented" when they said "modal". – rmaddy May 12 '14 at 23:48
-3
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

iCoder86
  • 1,874
  • 6
  • 26
  • 46