30

I have created an UIViewController sub class which can either be pushed in a navigation stack of a UINavigationController or presented(modally) from any UIViewController. I need to identify whether my view controller is presented, if it is presented I need to add a toolbar with a close button at the top of the view controller. (else if it is pushed in navigation stack then the default close button will get added, by using that the user can go back.)

In all the available versions say 4.3, 5.0, till 6.0, from inside an UIViewController sub class, Can I assume that the view controller is presented(modally) if the following condition is satisfied.

if(self.parentViewController == nil || self.navigationController == nil)
saikamesh
  • 4,569
  • 11
  • 53
  • 93
  • I think the answer to this question might help you: http://stackoverflow.com/questions/5338049/ios-how-to-check-if-a-modal-view-is-present – Jeremy Feb 26 '13 at 14:03

13 Answers13

37

With iOS 5, UIViewController gained a readonly property named presentingViewController, that replaces the older semantics of parentViewController (which now describes containment). This property can be used when a view controller needs to get at the view controller that’s presenting it — note: this will often be something else than what you’d expect, if you’re new to the API!

In addition, the isBeingPresented property had been introduced to pretty much solve the class of situations you’re currently in. Check for this property in your view controller’s viewWillAppear:.

Update

I overread that you seem to target iOS 4.3 as well:
In that case, you need to guard the call to isBeingPresented with an if ([self respondsToSelector:…]) you can then in the else block check for whether the parentViewController is not nil.

Another approach to backwards compatibility might be to override +resolveInstanceMethod: to add an implementation for -isBeingPresented at runtime. This will leave your calling sites clean, and you’d get rid of runtime-magic as soon as you let go of ancient iOS support ;-)

Note, though, that there are edge cases to this, and you initial approach as well, when running on iOS <5:

The view controller can be presented contained in any other view controller—including navigation controllers. When that last case happens, you’re out of luck: parentViewController will be nil, while navigationController will not. You can try to add gobs of unwieldy code to mitigate this limitation in older iOSes…or you could just let it go.

danyowdee
  • 4,658
  • 2
  • 20
  • 35
  • isBeingPresented is the exact one which I need to check whether to determine whether my view controller is presented or pushed in iOS 5.0 and above. But to support 4.3, I can not just check parentViewController is not nil, right?. Because parentViewController is not nil, in both cases pushing and presenting. parentViewController doc says "Prior to iOS 5.0, if a view did not have a parent view controller and was being presented, the presenting view controller would be returned." Also I dint understand adding implementation at runtime. Is it possible to elaborate?. – saikamesh Mar 06 '13 at 12:15
  • In iOS <5 `parentViewController` meant “the view controller that is presenting you”. IIRC, that **excludes** containment. You probably don’t want to be adding implementation at runtime, but if you’re really interested, check out the docs for [`+resolveInstanceMethod:`](http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/clm/NSObject/resolveInstanceMethod:), and [the guide](http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html). – danyowdee Mar 06 '13 at 13:32
15

I use the this code to check whether the UIViewController is presented.

if (uiviewcontroller.presentingViewController != nil) {
   // do something
}
Evelyn Loo
  • 321
  • 3
  • 8
  • 1
    If you are checking against your root view controller, this will always be nil. For the root controller, I check if self.presentedViewController == nil. If it's not nil, it means it's presenting something else and the root isn't visible anymore. – endavid Feb 20 '16 at 16:24
9

I had a similar case, however the view controller that I presented is wrapped in it's own navigation controller. So in that view controller when I need to determine whether or not to add the close button vs a back button, I just check the navigation controllers stack size. If the screen is presented, the stack size should be one (needs close button)... and if it is pushed using an existing navigation controller, then stack size will be larger than one (needs back button).

BOOL presented = [[self.navigationController viewControllers] count] == 1;
Martin M Reed
  • 145
  • 2
  • 9
7

To handle this kind of behavior, I usually set/reset a BOOL toggling it in viewWillAppear/viewWillDisappear methods.

By the way, your test condition seems incorrect. I think you should use

if(self.parentViewController != nil || self.navigationController != nil)

Why can't you just always add the toolbar to your view controller? Is there any case the view is loaded but never presented?

GUL
  • 207
  • 1
  • 8
5

In Swift on iOS 9 (or later):

if viewController.viewIfLoaded?.window != nil {
    // viewController is visible
}
Ali A. Jalil
  • 873
  • 11
  • 25
3

@saikamesh.

As you use UINavigationController to navigate your viewControllers, I think you can use topViewController (Doc here) and visibleViewController (Doc again) to reach your intention.

You mention that :

when it is pushed in navigation stack then the default close button will get added, by using that the user can go back

If the instance of the the specific UIViewController is important, I think it better to create a shared singleton instance and provide a global presented flag:

id specificVC = [SpecificViewController sharedInstance];
if (specificVC.isPushed) {
    [self.navController popToViewController:specificVC animated:YES];
}

and to check if it is presented:

if ([self.navController.visibleViewController isKindOfClass:[SpecificViewController class]]) {
    // Hide or add close button
    self.isPresented = YES;
}

Or, you can read the much accepted answer.

:) Hope helps.

Community
  • 1
  • 1
Jason Lee
  • 3,200
  • 1
  • 34
  • 71
  • 1
    And all of a sudden you need two instances, and you’re screwed because you got the Highlander on the lose. Decapitating helpless view controllers, exclaiming “There can be only one!” – danyowdee Mar 06 '13 at 08:22
2

Please check this way:

 for (UIViewController*vc in [self.navigationController viewControllers]) {
    if ([vc isKindOfClass: [OffersViewController class]]){ //this line also checks OffersViewController is presented or not 

        if(vc.isViewLoaded){
             NSLog(@"Yes");
        }

    }
}
Banker Mittal
  • 1,918
  • 14
  • 26
2

You could do it like this, it's fast and safe

UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

// Find the top controller on the view hierarchy
while (topController.presentedViewController) {
    topController = topController.presentedViewController;
}

// If the top controller it is not already presented
if (![topController isKindOfClass:[YourViewController class]]) {
    // Present it
    [topController presentViewController:yourViewController animated:YES completion:nil];
}
else {
// do some stuff here
}
Alejandro Luengo
  • 1,256
  • 1
  • 17
  • 27
1

You can at any point check whether you have a modal view controller presented or not by using the modalViewController property from your navigation controller. Ex:

   UIViewController *presentedController = self.navigationController.modalViewController;
   if (presentedController) {
      // At this point, you have a view controller presented from your navigation controller
      if ([presentedController isKindOfClass:[controllerYouWantToCheck class]]) {
         // add your toolbar/buttons/etc here
      }
   }
Vlad
  • 3,346
  • 2
  • 27
  • 39
1

One elegant answer that I haven't seen here:

// Edit: Added 2 other modal cases
extension UIViewController {
    var isModal: Bool { 
        return self.presentingViewController?.presentedViewController == self
            || (navigationController != nil && navigationController?.presentingViewController?.presentedViewController == navigationController)
            || tabBarController?.presentingViewController is UITabBarController
    }
}

credit: based on this gist

AmitaiB
  • 1,656
  • 1
  • 19
  • 19
1

As Martin Reed said, this is the best way

            BOOL presented = [[self.navigationController viewControllers] count] == 1;
        if (presented) {
            [self dismissViewControllerAnimated:YES completion:^{
                // do whatever you need here
            }];
        }
        else {
            [self.navigationController popViewControllerAnimated:YES];
        }
oskarko
  • 3,382
  • 1
  • 26
  • 26
0

If it was me I'd have a custom init method and use that when creating the vc.

vc = [[[MyUIViewControllerSubClass alloc] init] initWithToolbarAndCloseButton:YES];
ader
  • 5,403
  • 1
  • 21
  • 26
0

Small modification in @AmitaiB answer to create a function,

func isModallyPresented(tmpVC:UIViewController) -> Bool {
        return tmpVC.presentingViewController?.presentedViewController == tmpVC
            || (tmpVC.navigationController != nil && tmpVC.navigationController?.presentingViewController?.presentedViewController == tmpVC.navigationController)
            || tmpVC.tabBarController?.presentingViewController is UITabBarController
    }

Just check by calling:

if(isModallyPresented(tmpVC:myTopVC)){
//return true if viewcontroller is presented 
}
HDdeveloper
  • 4,396
  • 6
  • 40
  • 65