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?

DeyaEldeen
  • 10,847
  • 10
  • 42
  • 75
Zhen
  • 12,361
  • 38
  • 122
  • 199

17 Answers17

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.

Community
  • 1
  • 1
jburns20
  • 3,147
  • 2
  • 26
  • 32
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];
}
George
  • 2,187
  • 2
  • 17
  • 11
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;
    }
}
Chris
  • 3,577
  • 1
  • 21
  • 15
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.

tjklemz
  • 1,170
  • 12
  • 19
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
    }
}
Community
  • 1
  • 1
grassyburrito
  • 1,213
  • 22
  • 32
  • I've posted a modified version of this code that has Swift 3 support below as a new answer. – Cin316 May 28 '16 at 01: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
    }
}
Community
  • 1
  • 1
Cin316
  • 651
  • 1
  • 9
  • 19
  • 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 '16 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]
    }

}
Adam Johns
  • 35,397
  • 25
  • 123
  • 176
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.

Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345
  • 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 '12 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
    }
cdf1982
  • 764
  • 1
  • 18
  • 34
  • How can I get this as a string.. (I'm new to swift) – M_R_K May 01 '15 at 09: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 03 '15 at 10:15
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.

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];
}
Andrew
  • 1,344
  • 1
  • 12
  • 20
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]
    }
}
John Scalo
  • 3,194
  • 1
  • 27
  • 35
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.

Perception
  • 79,279
  • 19
  • 185
  • 195
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
}
Blake Lockley
  • 2,931
  • 1
  • 17
  • 30
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]
    }
}
baquiax
  • 148
  • 13
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
        }
    }
}
Oleg Korban
  • 249
  • 3
  • 14
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.

gandhi Mena
  • 2,115
  • 1
  • 19
  • 20