119

Is it possible to check inside ViewController class that it is presented as modal view controller?

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
lukewar
  • 2,715
  • 3
  • 19
  • 16

14 Answers14

98

Since modalViewController has been deprecated in iOS 6, here's a version that works for iOS 5+ and that compiles without warnings.

Objective-C:

- (BOOL)isModal {
    return self.presentingViewController.presentedViewController == self
      || (self.navigationController != nil && self.navigationController.presentingViewController.presentedViewController == self.navigationController)
      || [self.tabBarController.presentingViewController isKindOfClass:[UITabBarController class]];
}

Swift:

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

Hat tip to Felipe's answer.

Michael Waterfall
  • 20,497
  • 27
  • 111
  • 168
Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • 2
    good catch, I just had to use it again after a long time and noticed that the deprecation happened... I edited my answer so that people start looking here for the correct code when using iOS 6+, thanks – Felipe Sabino Apr 16 '14 at 22:59
  • 10
    Does not work if parent view controller is a modal on which our view controller is pushed. – meaning-matters May 12 '14 at 23:09
  • 2
    There's a bug, we should check if both sides are nil, because `nil == nil` returns `YES`, and it's not the result we want. – CocoaBob Feb 06 '15 at 15:25
  • @CocoaBob, thanks, that was indeed a corner case I did not consider. I've updated the answer to check for `nil` in the second case. – Gabriele Petronella Feb 09 '15 at 22:32
  • 1
    @GabrielePetronella Do you mind if I update the answer to also include a Swift implementation of the method? – Michael Waterfall Apr 02 '15 at 10:15
  • 1
    @MichaelWaterfall that'd be greatly appreciated, thanks – Gabriele Petronella Apr 02 '15 at 10:30
  • Also note that `self.presentingViewController?.presentedViewController == self` doesn't work if this code is run in a contained view controller. I think in that case the check needs to be something like `self.presentingViewController?.presentedViewController == parentViewController`. – Ryan Apr 27 '16 at 17:33
78

If you a looking for iOS 6+, this answer is deprecated and you should check Gabriele Petronella's answer


There is no neat way to do that, as a property or method native to UIKit. What you can do is to check several aspects of your controller to ensure it is presented as modal.

So, to check if the current (represented as self in the code bellow) controller is presented in a modal way or not, I have the function bellow either in a UIViewController category, or (if your project does not need to use other UIKit controllers, as UITableViewController for example) in a base controller that my other controllers inherit of

-(BOOL)isModal {

     BOOL isModal = ((self.parentViewController && self.parentViewController.modalViewController == self) || 
            //or if I have a navigation controller, check if its parent modal view controller is self navigation controller
            ( self.navigationController && self.navigationController.parentViewController && self.navigationController.parentViewController.modalViewController == self.navigationController) || 
            //or if the parent of my UITabBarController is also a UITabBarController class, then there is no way to do that, except by using a modal presentation
            [[[self tabBarController] parentViewController] isKindOfClass:[UITabBarController class]]);

    //iOS 5+
    if (!isModal && [self respondsToSelector:@selector(presentingViewController)]) {

        isModal = ((self.presentingViewController && self.presentingViewController.modalViewController == self) || 
             //or if I have a navigation controller, check if its parent modal view controller is self navigation controller
             (self.navigationController && self.navigationController.presentingViewController && self.navigationController.presentingViewController.modalViewController == self.navigationController) || 
             //or if the parent of my UITabBarController is also a UITabBarController class, then there is no way to do that, except by using a modal presentation
             [[[self tabBarController] presentingViewController] isKindOfClass:[UITabBarController class]]);

    }

    return isModal;        

}

EDIT: I added the last check to see if a UITabBarController is being used, and you present another UITabBarController as modal.

EDIT 2: added iOS 5+ check, where UIViewController does not answer for parentViewController anymore, but to presentingViewController instead.

EDIT 3: I've created a gist for it just in case https://gist.github.com/3174081

Community
  • 1
  • 1
Felipe Sabino
  • 17,825
  • 6
  • 78
  • 112
  • Keep in mind that the `modalViewController` property is deprecated as of iOS 6. The documentation suggests to use `presentedViewController` instead. – Bart Jacobs May 29 '13 at 09:51
  • @BartJacobs good point! I havent't looked at this answer after iOS6 release, so it might be not up-to-date. I will try to make some tests later in the week to update it, tks! – Felipe Sabino May 29 '13 at 12:26
  • `NSLog(@"%@", self.navigationController.parentViewController)` prints `(null)` - could you please explain why? My ViewController is connected with modal view controller through navController in storyboard. – Roman Apr 03 '14 at 10:34
  • @oyatek can you use pastebin or something similar and show some code? – Felipe Sabino Apr 03 '14 at 12:33
  • @Feilpe I found the problem - `.parentViewController` is deprecated, `.presentingViewController` must be used instead. – Roman Apr 03 '14 at 13:15
  • @oyatek I don't get it... thats exactly why I added the `iOS 5+` fallback with the `presentingViewController` logic... Isn't it working in some specific case you have? – Felipe Sabino Apr 03 '14 at 15:21
  • @oyatek I tested my code in the new ios and now I got what you meant, I edited my answer with the deprecation notice and you should look at Gabriele Petronella's answer - http://stackoverflow.com/a/16764496/429521 - for the correct code. Thanks :) – Felipe Sabino Apr 16 '14 at 22:57
35

In iOS5+, As you can see in UIViewController Class Reference, you can get it from property "presentingViewController".

presentingViewController The view controller that presented this view controller. (read-only)

@property(nonatomic, readonly) UIViewController *presentingViewController
Discussion

If the view controller that received this message is presented by another view controller, this property holds the view controller that is presenting it. If the view controller is not presented, but one of its ancestors is being presented, this property holds the view controller presenting the nearest ancestor. If neither the view controller nor any of its ancestors are being presented, this property holds nil.

Availability
Available in iOS 5.0 and later.
Declared In
UIViewController.h

Raj
  • 1,091
  • 1
  • 9
  • 18
  • 3
    Works perfectly, use if (self.presentingViewController) {//This is a modal viewContoller} else {//This is a normal ViewController} – mashdup Feb 26 '13 at 12:06
  • 2
    IMHO, this is the *only* correct answer here. Just check for the presence of a `presentingViewController`. It will also work in container view controllers, as it automatically traverses the ancestors. – Daniel Rinser Nov 26 '13 at 09:59
17

If there isn't, you can define a property for this (presentedAsModal) in your UIViewController subclass and set it to YES before presenting the ViewController as a modal view.

childVC.presentedAsModal = YES;
[parentVC presentModalViewController:childVC animated:YES];

You can check this value in your viewWillAppear override.

I believe there isn't an official property that states how the view is presented, but nothing prevents you from creating your own.

hpique
  • 119,096
  • 131
  • 338
  • 476
  • RIght and this is what i have did but I was looking for some other neat solution. Thanks. – lukewar May 10 '10 at 08:35
  • this solution does not work if you are presenting a `UINavigationController` as modal... unless you create a custom navigation controller just to add this property. And after that, inside the controllers, you will have to keep casting `self.navigationController` to this custom class every time you need to check if the controller is presented as modal – Felipe Sabino Dec 30 '11 at 11:27
8

Petronella's answer does not work if self.navigationController is modally presented but self is not equal to self.navigationController.viewControllers[0], in that case self is pushed.

Here is how you could fix the problem.

return self.presentingViewController.presentedViewController == self
            || (self.navigationController != nil && self.navigationController.presentingViewController.presentedViewController == self.navigationController && self == self.navigationController.viewControllers[0])
            || [self.tabBarController.presentingViewController isKindOfClass:[UITabBarController class]];

And in Swift:

return self.presentingViewController?.presentedViewController == self
        || (self.navigationController != nil && self.navigationController?.presentingViewController?.presentedViewController == self.navigationController && self.navigationController?.viewControllers[0] == self)
        || self.tabBarController?.presentingViewController is UITabBarController
Community
  • 1
  • 1
Semih Cihan
  • 634
  • 8
  • 5
6

This should work.

if(self.parentViewController.modalViewController == self)…
kubi
  • 48,104
  • 19
  • 94
  • 118
  • Unfortunately this does not work. It was my first try. But returned modalViewController ins nil :(. – lukewar May 10 '10 at 08:34
  • If you just get 'self.parentViewController' does it return the correct parent object? – kubi May 10 '10 at 09:35
  • 4
    The problem might be that your UIViewController subclass is inside a UINavigationController or a UITabBarController (or both), in which case you might need to dig a bit more in the view hierarchy to find out the parent that was presented as a modal view controller. – hpique May 10 '10 at 22:30
  • @hgpc I needed this chck in my project, so I just added an answer to check for both `UINavigationController` and `UITabBarController` cases. It is working pretty well so far – Felipe Sabino Sep 09 '11 at 20:05
5

Best way to check

 if (self.navigationController.presentingViewController) {
         NSLog(@"Model Present");
    }
Sunny Shah
  • 12,990
  • 9
  • 50
  • 86
2

If you don't need to distinguish between full-screen modal views and non-modal views, which is the case in my project (I was dealing with a problem that only occurs with form sheets and page sheets), you can use the modalPresentationStyle property of UIViewController:

switch (self.modalPresentationStyle) {
    case 0: NSLog(@"full screen, or not modal"); break;
    case 1: NSLog(@"page sheet"); break;
    case 2: NSLog(@"form sheet"); break;
}
arlomedia
  • 8,534
  • 5
  • 60
  • 108
2

In Swift:

func isUIViewControllerPresentedAsModal() -> Bool {
    if((self.presentingViewController) != nil) {
        return true
    }

    if(self.presentingViewController?.presentedViewController == self) {
        return true
    }

    if(self.navigationController?.presentingViewController?.presentedViewController == self.navigationController) {
        return true
    }

    if((self.tabBarController?.presentingViewController?.isKindOfClass(UITabBarController)) != nil) {
        return true
    }

    return false
}
King-Wizard
  • 15,628
  • 6
  • 82
  • 76
  • There is a Problem with this use case. If I'm in a root view controller of a UINavigationController it still returns true without any modal presentation. – mariusLAN Nov 27 '15 at 08:43
  • 1
    The first if statement covers everything that is in the second if statement, rendering the second statement redundant. I'm not sure what the intention is here. – isoiphone Jul 15 '16 at 18:11
1

In my project I have a view controller (Detail) that can be presented either modally (when adding a new item) or with push (when editing an existing one) by Master view controller. When user taps [Done] the Detail view controller calls Master view controller's method to notify that it is ready to be closed. Master has to determine how Detail is presented in order to know how to close it. This is how I do this:

UIViewController *vc = self.navigationController.viewControllers.lastObject;
if (vc == self) {
    [self dismissViewControllerAnimated:YES completion:NULL];
} else {
    [self.navigationController popViewControllerAnimated:YES];
}
Olex
  • 178
  • 1
  • 4
0

What worked for me is following:

// this is the trick: set parent view controller as application's window root view controller
UIApplication.sharedApplication.delegate.window.rootViewController = viewController;

// assert no modal view is presented
XCTAssertNil(viewController.presentedViewController);

// simulate button tap which shows modal view controller
[viewController.deleteButton sendActionsForControlEvents:UIControlEventTouchUpInside];

// assert that modal view controller is presented
XCTAssertEqualObjects(viewController.presentedViewController.class, MyModalViewController.class);

As far as I tested it, this works for iOS7 and iOS8. Didn't try on iOS6 however.

mixtly87
  • 1,675
  • 15
  • 32
0

A hack like this might work.

UIViewController* child = self;
UIViewController* parent = child.parentViewController;
while (parent && parent.modalViewController != child) {
    child = parent;
    parent = child.parentViewController;
}
if (parent) {
    // A view controller in the hierarchy was presented as a modal view controller
}

However, I think my previous answer is a cleaner solution.

hpique
  • 119,096
  • 131
  • 338
  • 476
0

I've looked a bit around to find the right answer to this question, and I couldn't find any which covered all the possible scenarios. I wrote these few lines of code which seem to do the job. You can find few inline comments to figure out what's been checked.

- (BOOL)isModal {
    BOOL modal = NO;
    if ([self presentingViewController]) { //Some view Controller is presenting the current stack
        UIViewController *presented = [[self presentingViewController] presentedViewController]; // What's been presented
        if ([presented respondsToSelector:@selector(viewControllers)]) { // There's a stack
            NSArray *viewControllers = [presented performSelector:@selector(viewControllers)];
            modal = [viewControllers firstObject] == self; // Current VC is presented modally if it's the first in the stack
        }
        else {
            modal = presented == self; // Don't think this is actually needed. set modal = YES should do the job tho.
        }
    }
    return modal;
}

Hope this help.

DennyLou
  • 313
  • 2
  • 4
0

Here's my modified version of @GabrielePetronella's isModal, which works with contained view controllers in that it walks up the parentViewController hierarchy first. Also pulled the code out into multiple lines so it's clear what it's doing.

var isModal: Bool {
    // If we are a child view controller, we need to check our parent's presentation
    // rather than our own.  So walk up the chain until we don't see any parentViewControllers
    var potentiallyPresentedViewController : UIViewController = self
    while (potentiallyPresentedViewController.parentViewController != nil) {
        potentiallyPresentedViewController = potentiallyPresentedViewController.parentViewController!
    }

    if self.presentingViewController?.presentedViewController == potentiallyPresentedViewController {
        return true
    }

    if let navigationController = potentiallyPresentedViewController.navigationController {
        if navigationController.presentingViewController?.presentedViewController == navigationController {
            return true
        }
    }

    return potentiallyPresentedViewController.tabBarController?.presentingViewController is UITabBarController
}
Community
  • 1
  • 1
Ryan
  • 606
  • 5
  • 12