79

I have 2 seperate navigationcontrollers, one with RootViewController A and the other with RootViewController B.

I am able to push ViewController C onto either A or B's navigation stack.

Question: When I am in ViewController C, how can I find out if I am in the stack belonging to A or B?

2
  • 7
    Why not just create a previousViewController property on C that you set when you push C? Or give C the info it need from A or B when you push it? Aug 10, 2011 at 4:25
  • 1
    @TerryWilcox , 'cos the relations could be very complicated nowadays
    – Vyacheslav
    Oct 24, 2016 at 13:54

17 Answers 17

107

You could use the UINavigationController's viewControllers property:

@property(nonatomic, copy) NSArray *viewControllers

Discussion: The root view controller is at index 0 in the array, the back view controller is at index n-2, and the top controller is at index n-1, where n is the number of items in the array.

https://developer.apple.com/documentation/uikit/uinavigationcontroller

You could use that to test whether the root view controller (the one at array index 0) is view controller A or B.

4
  • How can I get this as a string in SWIFT.. (I'm new to swift)
    – M_R_K
    May 1, 2015 at 9:13
  • @cyberboy just as easy: with var viewControllers: [UIViewController] Oct 6, 2015 at 19:38
  • 1
    Previous view controller will be at viewControllers.last
    – Burak
    Jun 13, 2016 at 11:04
  • @Burak viewControllers.last has current view controller, In order to get previous view controller one should use viewControllers[viewControllers.count-2]
    – Ganpat
    Oct 24, 2018 at 7:17
105

Here's the implementation of the accepted answer:

- (UIViewController *)backViewController
{
    NSInteger numberOfViewControllers = self.navigationController.viewControllers.count;

    if (numberOfViewControllers < 2)
        return nil;
    else
        return [self.navigationController.viewControllers objectAtIndex:numberOfViewControllers - 2];
}
1
  • 20
    I recommend adding this as a category on UIViewController
    – MaxGabriel
    Oct 15, 2013 at 0:34
29
- (UIViewController *)backViewController
{
    NSInteger myIndex = [self.navigationController.viewControllers indexOfObject:self];

    if ( myIndex != 0 && myIndex != NSNotFound ) {
        return [self.navigationController.viewControllers objectAtIndex:myIndex-1];
    } else {
        return nil;
    }
}
12

A more general implementation of the accepted answer:

- (UIViewController *)backViewController {
    NSArray * stack = self.navigationController.viewControllers;

    for (int i=stack.count-1; i > 0; --i)
        if (stack[i] == self)
            return stack[i-1];

    return nil;
}

This will return the correct "back view controller" regardless of where the current class is on the navigation stack.

0
9

Swift implementation of tjklemz code as an extension:

extension UIViewController {

    func backViewController() -> UIViewController? {        
        if let stack = self.navigationController?.viewControllers {
            for(var i=stack.count-1;i>0;--i) {
                if(stack[i] == self) {
                    return stack[i-1]
                }
            }
        }
        return nil
    }
}
1
  • I've posted a modified version of this code that has Swift 3 support below as a new answer.
    – Cin316
    May 28, 2016 at 1:03
9

Here's a modifed version of Prabhu Beeman's Swift code that adapts it to support Swift 3:

extension UIViewController {
    func backViewController() -> UIViewController? {
        if let stack = self.navigationController?.viewControllers {
            for i in (1..<stack.count).reverse() {
                if(stack[i] == self) {
                    return stack[i-1]
                }
            }
        }
        return nil
    }
}
1
  • a couple of notes: reverse() now is reversed() and the self.navigationController?.viewControllers never contains the "self" view controller, so return stack.last should be enough
    – Kappe
    Nov 14, 2016 at 22:54
5

As a UINavigationController extension:

extension UINavigationController {

    func previousViewController() -> UIViewController? {
        guard viewControllers.count > 1 else {
            return nil
        }
        return viewControllers[viewControllers.count - 2]
    }

}
3

Access the n-2 element of the viewControllers property to access the parent view controller.

Once you have that instance, you can check its type by logging what comes out of the NSStringFromClass() function. Or you could keep some static const identifier string in controllers A and B, and a getter function that prints out the string.

1
  • any simple way of getting the current index of a view controller or shall I hardcode them depending on my hierarchy structure.
    – HpTerm
    Nov 21, 2012 at 21:03
2

Swift implementation of @tjklemz code:

var backViewController : UIViewController? {

        var stack = self.navigationController!.viewControllers as Array

        for (var i = stack.count-1 ; i > 0; --i) {
            if (stack[i] as UIViewController == self) {
                return stack[i-1] as? UIViewController
            }

        }
        return nil
    }
2
  • How can I get this as a string.. (I'm new to swift)
    – M_R_K
    May 1, 2015 at 9:12
  • @cyberboy Hi! I'm not sure I understand your question. Do you want to get the backViewController description as a String? If so, you might consider to println("backViewController is: \(backViewController.description)"); this will give you a description like "UINavigationController: 0x7fcea1d286b0", which is not very clear but at least allows you to identify the object. As I said, I'm not sure of what you need exactly...
    – cdf1982
    May 3, 2015 at 10:15
1

Use the navigationController method to retrieve it. See documentation on Apple's site.

navigationController A parent or ancestor that is a navigation controller. (read-only)

@property(nonatomic, readonly, retain) UINavigationController *navigationController

Discussion Only returns a navigation controller if the view controller is in its stack. This property is nil if a navigation controller cannot be found.

Availability Available in iOS 2.0 and later.

1

Find the previous view controller is simple.

In your case, i.e., you are in C and you need B, [self.navigationController.viewControllers lastObject] is what you want.

For A, since A is the root view controller, you can just replace lastObject with firstObject to obtain.

0
1

Because dead horses enjoy being beaten :)

- (UIViewController *)previousViewController
{
    NSArray *vcStack = self.navigationController.viewControllers;
    NSInteger selfIdx = [vcStack indexOfObject:self];
    if (vcStack.count < 2 || selfIdx == NSNotFound) { return nil; }
    return (UIViewController *)[vcStack objectAtIndex:selfIdx - 1];
}
1

A more succinct Swift impementation:

extension UIViewController {
    var previousViewController: UIViewController? {
        guard let nav = self.navigationController,
              let myIdx = nav.viewControllers.index(of: self) else {
            return nil
        }
        return myIdx == 0 ? nil : nav.viewControllers[myIdx-1]
    }
}
0

Implementation for Swift 2.2 - Add this in an UIViewController extension. Safe in the sense it will return nil if the viewcontroller is the rootvc or not in a navigationcontroller.

var previousViewController: UIViewController? {
  guard let viewControllers = navigationController?.viewControllers else {
    return nil
  }
  var previous: UIViewController?
  for vc in viewControllers{
    if vc == self {
      break
    }
    previous = vc
  }
  return previous
}
0

With Swift using guard.

extension UIViewController {

    func getPreviousViewController() -> UIViewController? {
        guard let _ = self.navigationController else {
            return nil
        }

        guard let viewControllers = self.navigationController?.viewControllers else {
            return nil
        }

        guard viewControllers.count >= 2 else {
            return nil
        }        
        return viewControllers[viewControllers.count - 2]
    }
}
0

My extension, swift 3

extension UIViewController {
    var previousViewController: UIViewController? {
        guard let controllers = navigationController?.viewControllers, controllers.count > 1 else { return nil }
        switch controllers.count {
        case 2: return controllers.first
        default: return controllers.dropLast(2).first
        }
    }
}
0

for swift 3 you can do this:

var backtoViewController:UIViewController!
for viewController in (self.navigationController?.viewControllers)!.reversed() {
    if viewController is NameOfMyDestinationViewController {
            backtoViewController = viewController
    }
}
self.navigationController!.popToViewController(backtoViewController, animated: true)

You only need replace "NameOfMyDestinationViewController" by viewController that you want to return.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.