ChatGPT解决这个技术问题 Extra ChatGPT

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

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.


C
Cœur

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

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.
As I wrote, presentingViewController is always YES in my case; does not help.
presentingViewController returns YES for pushed VC, when there is a UITabBarController being set as a root. So, does not suitable in my case.
This does not work if you present a view controller then it pushes another one.
"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.
c
craft

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

Should be better in a var, like var isModal: Bool {}
@malinois is changed
What does the last false parameter in the return statement do?
you need change to let presentingIsNavigation = navigationController?.presentingViewController?.presentedViewController == navigationController && navigationController != nil
Swift 5: presentingIsNavigation = true if navigationController is nil
r
rmaddy

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

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.
You can't push a navigation controller. Perhaps you meant that you are presenting the navigation controller.
@jowie Use p, not po when printing a primitive value. po is for printing objects.
Documentation for isBeingPresented - This method returns YES only when called from inside the viewWillAppear: and viewDidAppear: methods.
@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.
A
Andrey Gordeev

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.


Why do you need to check tabBarController?.presentingViewController is UITabBarController ? Does it matter if that presentingViewController is also a UITabBarController?
And if navigationController is nil, isModal will return true. Is this intended?
n
nikans

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

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 :)
good job that deals with all the use cases. room for a bit of refactoring probably but still upvote !!
t
teradyl

Swift 5. Clean and simple.

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

C
Charlton Provatas

Swift 4

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

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).
M
Mehedi Hasan

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.


Y
Yevhen Dubinin

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

S
Senõr Ganso

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
}

which you should always do anyway - please explain why?
Alexander, you shouldn't, really.
A
Arash Zeinoddini

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


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

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

m
mkto
if let navigationController = self.navigationController, navigationController.isBeingPresented {
    // being presented
}else{
    // being pushed
}

J
JRG-Developer

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


Can still be in a modal navigation controller
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 The difference is between "pushed" and "presented". "Modal' has nothing to do with it. I believe "ColdLogic" meant "presented" when they said "modal".
b
blackjacx

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

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

For some one who's wondering, How to tell ViewController that it is being presented

if A is presenting/pushing B

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 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) } Usage in B View Controller override func viewDidLoad() { super.viewDidLoad() if self.vcPresentationStyle == .Present { //is being presented } else { //is being pushed } }


i
iCoder86
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


This property is deprecated.

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now