23

I have created a custom segue that presents a view controller inside a container that is very similar with Apple's own modal view controllers (I've implemented it as a UIViewController subclass).

I'm now trying to create a custom unwind segue but there's no way I can get the method -segueForUnwindingToViewController: fromViewController: identifier: to be called.

I've also implemented -viewControllerForUnwindSegueAction: fromViewController: withSender: on my container so I can point to the correct view controller (the one that presented this modal) but then the method that should be asked for my custom unwind segue doesn't get called anywhere.

Right now, the only way for me to dismiss this modal is to do it on the -returned: method.

Did anyone could successfully do this with a custom unwind segue?


EDIT: A little bit more code and context

My unwind view controller is configured in the storyboard, not programatically.

I have these pieces of code related to the unwind segues in my controllers:

PresenterViewController.m

I'm using a custom method to dismiss my custom modals here (-dismissModalViewControllerWithCompletionBlock:).

- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
                                      fromViewController:(UIViewController *)fromViewController
                                              identifier:(NSString *)identifier {
    return [[MyModalUnwindSegue alloc] initWithIdentifier:identifier
                                                   source:fromViewController
                                              destination:toViewController];
}

-(IBAction)returned:(UIStoryboardSegue *)segue {
    if ([segue.identifier isEqualToString:@"InfoUnwindSegue"]) {
        [self dismissModalViewControllerWithCompletionBlock:^{}];
    }
}

MyModalViewController.m

Here I only use -viewControllerForUnwindSegueAction: fromViewController: withSender: to point to the view controller that I should be unwind to.

- (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action 
                                      fromViewController:(UIViewController *)fromViewController 
                                              withSender:(id)sender {
    return self.myPresentingViewController;
}

The behavior I was expecting was that MyModalViewController was called to point to the view controller that should handle the unwinding and then this view controller had his -segueForUnwindingToViewController: fromViewController: identifier: method called before -returned: gets called.

Right now -segueForUnwindingToViewController: fromViewController: identifier: never gets called.

I must also say that I already tried different configurations. Everywhere I put my method to return the unwind segue it never gets called. I've read that I can subclass a navigation controller and then it gets called but I don't know how it would fit in my solution.


EDIT 2: Additional info

I've checked that MyModalViewController has his -segueForUnwindingToViewController: fromViewController: identifier: method called when I want to dismiss a regular modal view controller presented by it. This may be because he's the top most UIViewController in the hierarchy.

After checking this I've subclassed UINavigationController and used this subclass instead to contain my PresenterViewController. I was quite surprised to notice that his -segueForUnwindingToViewController: fromViewController: identifier: method is called as well.

I believe that only view controllers that serve as containers have this method called. That's something that makes little sense for me as they are not the only ones presenting other view controllers, their children are also doing so.

It's not OK for me to create logic in this subclass to choose which segue class to use as this class has no knowledge of what their children did.

Apple forums are down for the moment so no way to get their support right now. If anyone has any more info on how this works please help! I guess the lack of documentation for this is a good indicator of how unstable this still is.

Fábio Oliveira
  • 2,346
  • 21
  • 30

4 Answers4

11

To add to the answer from @Jeremy, I got unwinding from a modal UIViewController to a UIViewController contained within a UINavigationController to work properly (I.e how I expected it to) using the following within my UINavigationController subclass.

// Pass to the top-most UIViewController on the stack.
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)
             toViewController fromViewController:(UIViewController *)
             fromViewController identifier:(NSString *)identifier {
   UIViewController *controller = self.topViewController;
   return [controller segueForUnwindingToViewController:toViewController
                                  fromViewController:fromViewController
                                          identifier:identifier];
}

.. and then implementing the segueForUnwindingToViewController as usual in the actual ViewController inside the UINavigationController.

Ben Clayton
  • 80,996
  • 26
  • 120
  • 129
  • +1. This helped me. Using this new custom nav controller as my app's main nav controller. Hope nothing else breaks... – Jonny Feb 18 '14 at 06:45
  • Hmm it really messed up other places around my app so I reverted and went with another solution. Can't remove my +1 ;-) Anyway might be useful knowledge for later. – Jonny Feb 18 '14 at 09:50
  • To be honest not long after I discovered the above (which worked fine for me) I gave up entirely on segues and did everything in code. So, so, so much easier! Everything worked great! – Ben Clayton Feb 18 '14 at 09:53
  • 1
    Yes, I also used code for unwinding. I'm new to storyboards, and so far the overview is nice but Apple kind of failed in some ways, it seems just not possible to control the flow of the app totally only using a GUI made by Apple. :-P – Jonny Feb 18 '14 at 10:00
  • What is the benefit of this over having the proper segue returned in the UINavigationController's implementation of `segueForUnwindingToViewController:` ? – Petar Mar 03 '15 at 11:21
5

This method should be declared on the parent controller. So if you're using a Navigation Controller with a custom segue, subclass UINavigationController and define this method on it. If you would rather define it on one of the UINavigationController's child views, you can override canPerformUnwindSegueAction:fromViewController:withSender on the UINavigationController to have it search the children for a handler.

If you're using an embedded view (container view), then define it on the parent view controller.

See the last 10 minutes of WWDC 2012 Session 407 - Adopting Storyboards in Your App to understand why this works!

Jeremy Massel
  • 2,257
  • 18
  • 22
  • Can you pinpoint on my description in which view controller this should be declared? Can you find an explanation for the way it works? On my opinion the UINavigationController should ask their children for a unwind segue before giving his own as he wasn't the one calling and creating the original segue. Do you have a different explanation for why it works like this? – Fábio Oliveira Aug 13 '13 at 08:03
  • 1
    From your description, I'm not 100% sure. But the UINavigationController gets first dibs because it's likely that you want to unwind to the root, and from there it's more likely that it would be one of the farther back controllers. If you only wanted to back up one controller you probably wouldn't use an unwind segue, you'd probably just call popViewController. Ultimately, it looks like an optimization some frameworks engineer at Apple decided on – Jeremy Massel Aug 13 '13 at 15:01
0

If you're using a UINavigationController and your segue is calling pushViewController then in order to use a custom unwind segue you'll need to subclass UINavigationController and override - (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier.

Say I have a custom unwind segue called CloseDoorSegue. My UINavigationController subclass implementation might look something like:

- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {

UIStoryboardSegue* theSegue;

if ([identifier isEqualToString:@"CloseDoor"]) {
    theSegue = [CloseBookSegue segueWithIdentifier:identifier source:fromViewController destination:toViewController performHandler:^(void){}];
} else {
    theSegue = [super segueForUnwindingToViewController:toViewController fromViewController:fromViewController identifier:identifier];
}

return theSegue;

}

Set your UINavigationController subclass as the navigation controller class in the storyboard. You should be good to go provided you have setup the Exit event correctly with "CloseDoor" as the identifier. Also be sure to call 'popViewControllerAnimated' in your unwind segue instead of dismiss to keep in line with UINavigationControllers push/pop mechanism.

0

iOS development Library There is a discussion on iOS development Library along with this method. - segueForUnwindingToViewController:fromViewController:identifier: Make sure your MyModalViewController is the container role rather than a subcontroller of a container. If there is something like [anotherVC addChildViewController:myModalViewController];,you should put the segueForUnwindingToViewController method in some kind of "AnotherVC.m" file.

Discussion If you implement a custom container view controller that also uses segue unwinding, you must override this method. Your method implementation should instantiate and return a custom segue object that performs whatever animation and other steps that are necessary to unwind the view controllers.

musixlemon
  • 61
  • 2
  • 5