Instead of push segue, how do I replace the view controller (or remove it from the navigation stack)?

I have a small application for the iPhone , in which the navigation controller displays 3 views (here full screen ):

Xcode screenshot

First, it displays a list of social networks (Facebook, Google+, etc.):

list screenshot

It then displays an OAuth dialog asking for credentials:

login screenshot

And (after that in the same UIWebView ) for permissions:

permissions screenshot

Finally, it displays the last view controller with user data (in a real application, this will be the menu where you can start the multiplayer game):

details screenshot

All this works well, but I have a problem when the user wants to come back and choose another social network:

The user accesses the "Back" button and instead of displaying the first view, the second is displayed, asking for OAuth credentials / permissions again.

What can i do here? Xcode 5.0.2 shows a very limited choice for segues β€” push , modal (which I cannot use because it hides the navigation bar needed for my game) and ordering .

I am new to iOS programming, but I previously developed the Adobe AIR mobile application , and it was possible there: 1) replace the view instead and 2) remove the unnecessary view from the navigation stack.

How to do the same in a native application?

+59
ios objective-c iphone segue uistoryboardsegue
Jan 28 '14 at 19:19
source share
14 answers

You can use custom segue: for this you need to create a subclass of the UIStoryboardSegue class (MyCustomSegue example), and then you can override "execute" 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 (MyCustomSegue example)

+37
Jan 28 '14 at 19:41
source share
β€” -

To deploy at different levels above, this is my solution. It has the following advantages:

  • It can work anywhere in the presentation stack, and not just in the top view (not sure if it is really ever necessary or even technically possible to run it, but it is there).
  • This does not lead to the default transition to the previous view controller before displaying the replacement, it just displays the new controller with a natural transition, while the reverse navigation goes to the same reverse 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]; } 
+56
Oct 21 '14 at 3:37
source share

Custom transition did not work for me, since I had a Splash view controller, and I wanted to replace it. Since there was only one view controller in the list, popToRootViewController still left Splash on the stack. I used the following code to replace one 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) } } 
+22
Feb 21 '14 at 18:49
source share

operational version of ima747:

 override func perform() { let navigationController: UINavigationController = sourceViewController.navigationController!; var controllerStack = navigationController.viewControllers; let index = controllerStack.indexOf(sourceViewController); controllerStack[index!] = destinationViewController navigationController.setViewControllers(controllerStack, animated: true); } 

As he mentioned, it has the following advantages:

  • It can work anywhere in the presentation stack, and not just in the top view (not sure if it is really ever necessary or even technically possible to run it, but it is there).
  • This does not lead to the default transition to the previous view controller before displaying the replacement, it just displays the new controller with a natural transition, while the reverse navigation goes to the same reverse navigation of the source controller.
+17
Oct 26 '15 at 23:36
source share

For this problem, I think the answer is as simple as

  • Get an array of view controllers from a NavigationController
  • Removing the last ViewController (current view controller)
  • Insert a new file
  • Then set the ViewControllers array back to the navigationController as below:

      if let navController = self.navigationController { let newVC = DestinationViewController(nibName: "DestinationViewController", bundle: nil) var stack = navController.viewControllers stack.remove(at: stack.count - 1) // remove current VC stack.insert(newVC, at: stack.count) // add the new one navController.setViewControllers(stack, animated: true) // boom! } 

works great with Swift 3.

Hope this helps some new guys.

Greetings.

+14
Nov 23 '16 at 17:43
source share

Using unwinding segue would be the most appropriate solution to this problem. I agree with Lauro.

The following is a brief explanation for setting segue unwinding from detailsViewController [or viewController3] to myAuthViewController [or viewController1]

This, in essence, is how you could unwind segue through code.

  • Implement the IBAction method in the viewController that you want to disable (in this case, viewController1). The method name can be any so long that one argument of type UIStoryboardSegue is required.

     @IBAction func unwindToMyAuth(segue: UIStoryboardSegue) { println("segue with ID: %@", segue.Identifier) } 
  • Bind this method in the viewController (3) that you want to disable. To link, right-click (double-click) the exit icon at the top of the viewController, at which point the "unwindToMyAuth" method will appear in a pop-up window. Control the click from this method to the first icon, the viewController icon (also present at the top of the viewController, on the same line as the exit icon). Select the β€œmanual” option that appears.

  • In the document schema for the same view (viewController3), select the newly created unwind section. Go to the designated inspector and assign a unique identifier to this unwind session. Now we have a common U-turn, ready to use.

  • Now, the spread can be performed in the same way as any other code from the code.

     performSegueWithIdentifier("unwind.to.myauth", sender: nil) 

This approach will lead you from viewController3 to viewController1 without having to remove viewController2 from the navigation hierarchy.

Unlike other segues, unind segues do not instantiate a view controller, they only go to the existing view controller in the navigation hierarchy.

+9
Jan 10 '15 at 18:15
source share

As mentioned in previous answers, a pop-up window without animation, and then adding an animation does not look very good, because the user will see the process itself. I recommend clicking the animation first and then deleting the previous VC. Like this:

 extension UINavigationController { func replaceCurrentViewController(with viewController: UIViewController, animated: Bool) { pushViewController(viewController, animated: animated) let indexToRemove = viewControllers.count - 2 if indexToRemove >= 0 { viewControllers.remove(at: indexToRemove) } } } 
+4
Aug 26 '17 at 7:21
source share

Use a downstream controller of the last code You can use another button or add it instead of the cancel button that I used

 - (void)viewDidLoad { [super viewDidLoad]; [self.navigationController setNavigationBarHidden:YES]; UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismiss:)]; self.navigationItemSetting.leftBarButtonItem = cancelButton; } - (IBAction)dismissSettings:(id)sender { // your logout code for social media selected [self.navigationController popToRootViewControllerAnimated:YES]; } 
+2
Jan 29 '14 at 10:17
source share

What you really have to do is modally introduce a UINavigationController containing the UIViewControllers social network, the top of your UIViewController menu (which can be built into the UINavigationController if you want). Then, after user authentication, you turn off the social network UINavigationController , again showing your UIViewController menu.

+2
Nov 13 '14 at 19:34
source share

In swift3 create one segment identifier -add -add and set in segue (storyboard) a custom storyboard class from the cocoatouch file -Class override interface ()

 override func perform() { let sourceViewController = self.source let destinationController = self.destination let navigationController = sourceViewController.navigationController // Pop to root view controller (not animated) before pushing if self.identifier == "your identifier"{ navigationController?.popViewController(animated: false) navigationController?.pushViewController(destinationController, animated: true) } } 

-You must also override one method in your original control view.

  override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { return false } 
+2
Nov 17 '16 at 7:36
source share

Well, what you can also do is use the material of the unwind manager.

In fact, I think this is exactly what you need.

Check this entry: What are Unwind segues and how do you use them?

+1
Jan 29 '14 at 10:11
source share

This worked for me in Swift 3:

 class ReplaceSegue: UIStoryboardSegue { override func perform() { if let navVC = source.navigationController { navVC.pushViewController(destination, animated: true) } else { super.perform() } } } 
+1
Oct 11 '16 at 1:15
source share

How about this :) I have an old question now, but it will work like a charm

 UIViewController *destinationController = [[UIViewController alloc] init]; UINavigationController *newNavigation = [[UINavigationController alloc] init]; [newNavigation setViewControllers:@[destinationController]]; [[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation; 
+1
Jun 21 '18 at 1:01
source share

Here's a quick Swift 4/5 solution by creating a custom effect that replaces the (top stack) viewcontroller with a new one (no animation):

 class SegueNavigationReplaceTop: UIStoryboardSegue { override func perform () { guard let navigationController = source.navigationController else { return } navigationController.popViewController(animated: false) navigationController.pushViewController(destination, animated: false) } } 
+1
May 02 '19 at 11:08
source share



All Articles