Instead of push segue how to replace view controller (or remove from navigation stack)?

To expand on the various segues above, this is my solution. It has the following advantages:

  • Can work anywhere in the view stack, not just the top view (not sure if this is realistically ever needed or even technically possible to trigger, but hey it's in there).
  • It doesn't cause a pop OR transition to the previous view controller before displaying the replacement, it just displays the new controller with a natural transition, with the back navigation being to the same back navigation of the source controller.

Segue Code:

- (void)perform {
    // Grab Variables for readability
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;

    // Get a changeable copy of the stack
    NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
    // Replace the source controller with the destination controller, wherever the source may be
    [controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];

    // Assign the updated stack with animation
    [navigationController setViewControllers:controllerStack animated:YES];
}

You could use a custom segue: to do it you need to create a class subclassing UIStoryboardSegue (example MyCustomSegue), and then you can override the "perform" with something like this

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    // Pop to root view controller (not animated) before pushing
    [navigationController popToRootViewControllerAnimated:NO];
    [navigationController pushViewController:destinationController animated:YES];    
}

At this point go to Interface Builder, select "custom" segue, and put the name of your class (example MyCustomSegue)


The custom segue didn't work for me, as I had a Splash view controller and I wanted to replace it. Since there was just one view controller in the list, the popToRootViewController still left the Splash on the stack. I used the following code to replace the single controller

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    [navigationController setViewControllers:@[destinationController] animated:YES];
}

and now in Swift 4:

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        source.navigationController?.setViewControllers([destination], animated: true)
    }
}

and now in Swift 2.0

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        sourceViewController.navigationController?.setViewControllers([destinationViewController], animated: true)
    }
}