98

I want to pop to the third view on the navigation stack back to the first view.

I know how to pop one view at once:

[self.navigationController popViewControllerAnimated:YES];

But how do I do two at once?

4b0
  • 21,981
  • 30
  • 95
  • 142
Adam Waite
  • 19,175
  • 22
  • 126
  • 148
  • 2
    Meta comment: @lubilis answer all the way down there is the best. The top rated answers were good in their time but are no longer relevant. – n13 Oct 24 '16 at 08:49

18 Answers18

135

You can try this to Jump between the navigation controller stack as well

NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
for (UIViewController *aViewController in allViewControllers) {
    if ([aViewController isKindOfClass:[RequiredViewController class]]) {
        [self.navigationController popToViewController:aViewController animated:NO];
    }
}
Daniel
  • 20,420
  • 10
  • 92
  • 149
Meet
  • 4,904
  • 3
  • 24
  • 39
  • 6
    imo this is the best method by far , but you should reference your uinavigation controller using self.navigationcontroller – henghonglee Aug 15 '12 at 10:18
  • 2
    I agree, this is the best solution if the user would like to pop the stack back to a certain viewcontroller. Let's say you don't know which viewcontroller that is, you can still implement a system where you would specify how many viewcontrollers you would like to pop off the stack and get the target viewcontroller from the allViewControllers array with objectAtIndex:(allViewControllers.count - 1 - amount). -1 because arrays are zero-based ofcourse. – Jovan Oct 18 '12 at 12:44
  • 3
    Works also when iterating over the original navigator->viewControllers array (no need converting it to a mutable array) – Arik Segal Feb 25 '16 at 15:09
  • 1
    NOTE! That this will iterate the stack from the beginning -> to the end, to be exactly correct you should reverse the stack. Because if you have several VC's of the same kind you will remove the wrong VC in the wrong order in the stack. – StackUnderflow Jun 28 '17 at 23:30
80

Here are two UINavigationController extensions that can solve your problem. I would recommend using the first one that pops to a UIViewController of a specific class:

extension UINavigationController {

  func popToViewController(ofClass: AnyClass, animated: Bool = true) {
    if let vc = viewControllers.filter({$0.isKind(of: ofClass)}).last {
      popToViewController(vc, animated: animated)
    }
  }

  func popViewControllers(viewsToPop: Int, animated: Bool = true) {
    if viewControllers.count > viewsToPop {
      let vc = viewControllers[viewControllers.count - viewsToPop - 1]
      popToViewController(vc, animated: animated)
    }
  }

}

and use it like this:

// pop to SomeViewController class
navigationController?.popToViewController(ofClass: SomeViewController.self)

// pop 2 view controllers
navigationController?.popViewControllers(viewsToPop: 2)
budiDino
  • 13,044
  • 8
  • 95
  • 91
  • 3
    For Swift (option 1), you can replace the two `removeLast` with just `removeLast(2)`. – Dominic K May 10 '16 at 18:40
  • What about calling methods of controllers's lifecycle? DidAppear and ect – Mike Glukhov Sep 07 '17 at 07:33
  • 1
    (Swift 4) You missing brackets in this line `let vc = viewControllers[viewControllers.count - viewsToPop + 1]`, in order to work correctly you need to replace it with: `let vc = viewControllers[viewControllers.count - (viewsToPop + 1)]` or `let vc = viewControllers[viewControllers.count - viewsToPop - 1]` – MMiroslav Sep 26 '18 at 10:53
  • @MMiroslav your are right. I've updated my answer. Thank you / Hvala ;) – budiDino Sep 26 '18 at 14:16
  • 1
    This answer has been updated after I posted my answer below. Essentially a copy of my code ^_^ – lubilis Mar 22 '20 at 13:16
  • @lubilis I've been updating and maintaining this answer since the first time I've posted it in 2014. I even left a comment on your answer and wrote how it inspired me to improve my answer and what I did differently. – budiDino Mar 22 '20 at 19:56
46

You can pop to the "root" (first) view controller with popToRootViewControllerAnimated:

[self.navigationController popToRootViewControllerAnimated:YES];

// If you want to know what view controllers were popd:
// NSArray *popdViewControllers = [self.navigationController popToRootViewControllerAnimated:YES];

UINavigationController Reference:

Pops all the view controllers on the stack except the root view controller and updates the display.

Return Value
An array of view controllers that are popped from the stack.

Benedikt Köppel
  • 4,853
  • 4
  • 32
  • 42
chown
  • 51,908
  • 16
  • 134
  • 170
29
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];   

objectAtIndex:1 --> you can pass whichever index you want to pop to

Zaraki
  • 3,720
  • 33
  • 39
19

Swift 2 - xCode 7.3

This could be a very useful extension to pop UIViewControllers:

extension UINavigationController {

    func popToViewControllerOfType(classForCoder: AnyClass) {
        for controller in viewControllers {
            if controller.classForCoder == classForCoder {
                popToViewController(controller, animated: true)
                break
            }
        }
    }

    func popViewControllers(controllersToPop: Int, animated: Bool) {
        if viewControllers.count > controllersToPop {
            popToViewController(viewControllers[viewControllers.count - (controllersToPop + 1)], animated: animated)
        } else {
            print("Trying to pop \(controllersToPop) view controllers but navigation controller contains only \(viewControllers.count) controllers in stack")
        }
    }
}
lubilis
  • 3,942
  • 4
  • 31
  • 54
  • 2
    This needs more upvoting.. .I was just about to write that extension. I'll just use yours thanks ;) – n13 Oct 24 '16 at 08:45
  • 1
    @n13 how come this is better than budidino's answer? – Crashalot Jan 04 '17 at 09:00
  • 1
    Using an extension makes for much cleaner code. You could take budidino's answer and make an extension out of it but this one saves you the effort. Also this answer checks for error cases, and handles errors gracefully (e.g. trying to pop more than you have) – n13 Jan 04 '17 at 13:15
  • 1
    I like this answer. Made me rethink and update the answer I posted. I made my code pop to the last instance of the searched class in the viewControllers array because that is probably the desired behavior. – budiDino Aug 02 '18 at 06:37
15

If you just want to pop 2 at once because your rootViewController is (way) 'deeper' then 2 you could consider adding a category to UIviewController for example:

UINavigationController+popTwice.h

#import <UIKit/UIKit.h>
@interface UINavigationController (popTwice)

- (void) popTwoViewControllersAnimated:(BOOL)animated;

@end

UINavigationController+popTwice.m

#import "UINavigationController+popTwice.h"

@implementation UINavigationController (popTwice)

- (void) popTwoViewControllersAnimated:(BOOL)animated{
    [self popViewControllerAnimated:NO];
    [self popViewControllerAnimated:animated];
}

@end

Import the category #import "UINavigationController+popTwice.h" somewhere in your implementation and use this line of code to pop 2 controllers at once:

[self.navigationController popTwoViewControllersAnimated:YES];

isn't that great? :)

Tieme
  • 62,602
  • 20
  • 102
  • 156
  • 6
    what if you need to pop three view, you'll write "UINavigationController+popThrice.m"?????? – Meet Aug 22 '12 at 06:54
  • 8
    you could just pass in a parameter for the number of viewControllers to pop, and wrap [self popViewControllerAnimated:NO]; within a for-loop, of count-1. – noRema Nov 12 '12 at 15:40
  • This is not the correct way, if you want to pot to 2,3,...any controller identify it by loop and then use [self.navigationController popToViewControllerAnimated:YES];. This above mentioned is very bad coding and may show flickered UI and bad user experience. – codelover May 05 '17 at 09:10
13

Swift 4 :

func popViewControllerss(popViews: Int, animated: Bool = true) {
    if self.navigationController!.viewControllers.count > popViews
    {
        let vc = self.navigationController!.viewControllers[self.navigationController!.viewControllers.count - popViews - 1]
         self.navigationController?.popToViewController(vc, animated: animated)
    }
}

Then Use This Method

self.popViewControllerss(popViews: 2)
Saifan Nadaf
  • 1,715
  • 1
  • 17
  • 28
6
//viewIndex = 1;
//viewIndex = 2;
//viewIndex = 3;

// **[viewControllers objectAtIndex: *put here your viewindex number*]

NSArray *viewControllers = [self.navigationController viewControllers];
[self.navigationController popToViewController:[viewControllers objectAtIndex:viewIndex] animated:NO];
richsage
  • 26,912
  • 8
  • 58
  • 65
Sabareesh
  • 3,585
  • 2
  • 24
  • 42
6

You can also try this one :-

[self.navigationController popToViewController:yourViewController animated:YES];
TheTiger
  • 13,264
  • 3
  • 57
  • 82
Leena
  • 2,678
  • 1
  • 30
  • 43
4
    NSMutableArray *newStack = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
    [newStack removeLastObject];
    [newStack removeLastObject];
    [self.navigationController setViewControllers:newStack animated:YES];
patrick-fitzgerald
  • 2,561
  • 3
  • 35
  • 49
4

In Swift 3, you can pop one, two or more viewcontrollers from navigation controller like that

let viewControllers = self.navigationController!.viewControllers as [UIViewController]
    for aViewController:UIViewController in viewControllers {
        if aViewController.isKind(of: FromWhereYouWantToGoController.self) {
            _ = self.navigationController?.popToViewController(aViewController, animated: true)
        }
    }

Here FromWhereYouWantToGoController is destination controller. Hope it helps.

Riajur Rahman
  • 1,976
  • 19
  • 28
3

You can pass the initial view controller (the one you want to come back to) and then call this line in the last view:

[self.navigationController popToViewController:yourInitialViewController animated:YES];

L.

Lucas
  • 1,135
  • 1
  • 9
  • 20
3

I didn't see this answer in here. If you only want to pop a few (not all the way to the root), you can just iterate through self.navigationController.viewControllers checking for class types, or if you have a reference use that:

for (UIViewController *aViewController in self.navigationController.viewControllers) {
   if ([aViewController isKindOfClass:[SMThumbnailViewController class]]) {
      [self.navigationController popToViewController:aViewController animated:YES];
   }
}
jose920405
  • 7,982
  • 6
  • 45
  • 71
VaporwareWolf
  • 10,143
  • 10
  • 54
  • 80
2

Here's a little function I'm using to go back X ViewControllers:

-(void)backMultiple:(id)data {
    int back = 2; //Default to go back 2 
    int count = [self.navigationController.viewControllers count];

    if(data[@"count"]) back = [data[@"count"] intValue];

    //If we want to go back more than those that actually exist, just go to the root
    if(back+1 > count) {
        [self.navigationController popToRootViewControllerAnimated:YES];
    }
    //Otherwise go back X ViewControllers 
    else {
        [self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:count-(back+1)] animated:YES];
    }
}
Mark
  • 368
  • 1
  • 9
2

Swift Version as of (Swift 1.2 / Xcode 6.3.1) of popping twice or more

 var viewControllers = self.navigationController?.viewControllers
 viewControllers?.removeLast()
 viewControllers?.removeLast()
 self.navigationController?.setViewControllers(viewControllers, animated: true)

or

 var viewControllers = self.navigationController?.viewControllers
 var viewsToPop = 2
 var end = (viewControllers?.count)!
 var start = end - viewsToPop
 viewControllers?.removeRange(Range<Int>(start: start, end: end))
 self.navigationController?.setViewControllers(viewControllers, animated: true)
Glenn S
  • 697
  • 9
  • 21
2

you can pop back to the root view controller

[self.navigationController popToRootViewControllerAnimated:YES];

or, if the view you want to pop to isn't the first one, you'll need to pop again in your previous view's viewWillAppear

Robotnik
  • 3,643
  • 3
  • 31
  • 49
1

You can use the stack of the UIViewControllers. 1. Fetch the array of all the viewControllers in the stack. 2. Iterate through the whole array and find the desired viewController
by matching the class type. 3. Pop the viewController.

func popToSpecificViewC
{
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController];
        for viewController:UIViewController in viewControllers
        {
            if viewController.isKind(of: WelcomeViewC.self)
            {
                _ = self.navigationController?.popToViewController(viewController, animated: true)
            }
        }
}
Peeyush karnwal
  • 622
  • 7
  • 24
0

Using a simple UINavigationController extension:

extension UINavigationController {
    func popViewControllers(_ count: Int) {
        guard viewControllers.count > count else { return }
        popToViewController(viewControllers[viewControllers.count - count - 1], animated: true)
    }
}
Balázs Vincze
  • 3,550
  • 5
  • 29
  • 60