Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unwind through multiple views without displaying intermediate views

Assume we have three view controllers: 1, 2, and 3. Using the storyboard, it's pretty simple to unwind from view controller 3 to view controller 1 using an unwind segue. However, when unwinding, view controller 2 is briefly visible before view controller 1 is displayed. Is there any way to get from 3 to 1 without displaying 2 again?

View Controller 1:

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  NSLog(@"one did appear");
}

- (IBAction)goToTwo:(id)sender {
  NSLog(@"#### segue to two");
  [self performSegueWithIdentifier:@"TwoSegue" sender:self];
}

- (IBAction)unwindToOne:(UIStoryboardSegue *)sender {
}

View Controller 2:

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  NSLog(@"two did appear");
}
- (IBAction)goToThree:(id)sender {
  NSLog(@"#### segue to three");
  [self performSegueWithIdentifier:@"ThreeSegue" sender:self];
}

View Controller 3:

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  NSLog(@"three did appear");
}

- (IBAction)unwindToOne:(id)sender {
  NSLog(@"#### unwind to one");
  [self performSegueWithIdentifier:@"OneSegue" sender:self];
}

This produces the following log messages:

  • one did appear
  • segue to two
  • two did appear
  • segue to three
  • three did appear
  • unwind to one
  • two did appear
  • one did appear

I've tried using custom segues and disabling animation. Although removing animation makes view controller 2 appear for an even shorter period of time, it still appears. Is there any way to program this behavior?

Screenshot of storyboard:

enter image description here

like image 760
Craig Avatar asked Nov 06 '14 17:11

Craig


People also ask

How do you make an unwind segue in a storyboard?

In your storyboard, create an unwind segue by right-clicking a triggering object and dragging to the Exit control at the top of your view controller's scene.

How to pass data using segue and unwind in Swift?

To perform Unwind segues: To connect an unwind segue, you first need to mark the destination view controller with an unwind action. Like other action methods, unwind actions are marked with the @IBAction keyword, but they get a UIStoryboardSegue as a parameter and not a sender.

How to segue between view controllers Swift?

Well, in addition to that, Ctrl+dragging also helps set up segues. So, Ctrl+Drag from the “Go to Other View Controller” button, to somewhere in the second View Controller. It can be anywhere in the main box of the second view controller. When you release, it will show you a box like the one below.

How to do a segue?

To create a segue between view controllers in the same storyboard file, Control-click an appropriate element in the first view controller and drag to the target view controller. The starting point of a segue must be a view or object with a defined action, such as a control, bar button item, or gesture recognizer.


1 Answers

This seems to be due to the way that unwind segues search for the nearest view controller which implements the unwind action you specified in the storyboard. From the documentation:

How an Unwind Segue Selects its Destination

When an unwind segue is triggered, it must locate the nearest view controller that implements the unwind action specified when the unwind segue was defined. This view controller becomes the destination of the unwind segue. If no suitable view controller is found, the unwind segue is aborted. The search order is as follows:

A viewControllerForUnwindSegueAction:fromViewController:withSender: message is sent to the parent of the source view controller.

A viewControllerForUnwindSegueAction:fromViewController:withSender: message is sent to the next parent view controller [...]

You can override canPerformUnwindSegueAction:fromViewController:withSender: if you have specific requirements for whether your view controller should handle an unwind action.

The view controllers in your example don't have a parent, so it seems that the fallback (which I can't see documentation for) is to instantiate each presentingViewController and call canPerformUnwindSegueAction on them in turn. Returning NO from this method in ViewControllerTwo doesn't prevent its instantiation and display, so that doesn't solve the issue.

I've tried your code embedded within a navigation controller, and it works fine. This is because in that case, UINavigationController is the parent of each of your view controllers, and it handles all the selection of a destination view controller. More documentation:

Container View Controllers

Container view controllers have two responsibilities in the unwind process, both discussed below. If you are using the container view controllers provided by the SDK, such as UINavigationController, these are handled automatically.

If you were to create a simple container view controller to act as the parent for your three view controllers, you could use its viewControllerForUnwindSegueAction method to check each of its child controllers for the existence of the unwind action, before calling canPerformUnwindSegueAction on that controller, and finally returning the first one of those which returns YES.

Selecting a Child View Controller to Handle An Unwind Action

As mentioned in How an Unwind Segue Selects its Destination, the source view controller of an unwind segue defers to its parent to locate a view controller that wants to handle the unwind action. Your container view controller should override the method shown in Listing 2 to search its child view controllers for a view controller that wants to handle the unwind action. If none of a container's child view controllers want to handle the unwind action, it should invoke the super's implementation and return the result.

A container view controller should search its children in a way that makes sense for that container. For example, UINavigationController searches backwards through its viewControllers array, starting from the view at the top of the navigation stack.

Listing 2 Override this method to return a view controller that wants to handle the unwind action. - (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender

Container view controller design has a whole article dedicated to it by Apple, which I won't duplicate here (more than enough of Apple's writing in this answer already!) but it looks like it will take some thought to get it right, as it depends on the exact role you want these to play in your application.

A quick workaround, to get your desired behaviour using unwind segues, could be to embed the view controllers in a UINavigationController, and then hide the navigation bar using

[self.navigationController setNavigationBarHidden:YES];
like image 185
Josh Heald Avatar answered Oct 15 '22 18:10

Josh Heald