17

I've just figured out what is an unwind segue and how to use it with the great answers from this question. Thanks a lot.

However, here's a question like this:

Let's say there is a button in scene B which unwind segues to scene A, and before it segues I want something to be done, such as saving the data to database. I created an action for this button in B.swift, but it seems it goes directly to scene A without taking the action appointed.

Anyone knows why or how to do this?

Thank you.

Community
  • 1
  • 1
Mechanic
  • 173
  • 1
  • 1
  • 5
  • If you are using an actual unwind segue (as opposed to a simple back button in a `UINavigationController` then `prepareForSegue` will be called in both source and destination view controllers - https://developer.apple.com/library/ios/technotes/tn2298/_index.html#//apple_ref/doc/uid/DTS40013591-CH1-UNWINDPROC You will need to give your unwind segue and identifier in the storyboard if you haven't already done so in order to identify it in `prepareForSegue` – Paulw11 Feb 07 '15 at 10:29
  • `prepareForSegue` is called only in the source view controller actually. – Nickkk Jun 20 '16 at 23:05

3 Answers3

22

You can do it the way you describe, or by using a prepareForSegue override function in the viewController you are unwinding from:

@IBAction func actionForUnwindButton(sender: AnyObject) {
    println("actionForUnwindButton");
}

or...

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    println("prepareForSegue");
}

The first example is as you describe. The button is wired up to the unwind segue and to the button action in Interface Builder. The button action will be triggered before the segue action. Perhaps you didn't connect the action to the button in interface builder?

The second example gives you have access to the segue's sourceViewController and destinationViewController in case that is also useful (you also get these in the unwind segue's function in the destination view controller).

If you want to delay the unwind segue until the button's local action is complete, you can invoke the segue directly from the button action (instead of hooking it up in the storyboard) using self.performSegueWithIdentifier (or follow wrUS61's suggestion)

EDIT

you seem to have some doubts whether you can work this by wiring up your button both to an unwind segue and to a button action. I have set up a little test project like this:

class BlueViewController: UIViewController {

    @IBAction func actionForUnwindButton(sender: AnyObject) {
        println("actionForUnwindButton");
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        println("prepareForSegue");
    }
}


class RedViewController: UIViewController {

    @IBAction func unwindToRed(sender: UIStoryboardSegue) {
        println("unwindToRed");
    }
}

BlueViewController has a button that is connected in the storyboard to BOTH the unwindToRed unwind segue AND the actionForUnwindButton button. It also overrides prepareForSegue so we can log the order of play.

Output:

actionForUnwindButton
prepareForSegue
unwindToRed

Storyboard:

enter image description here

EDIT 2

your demo project shows this not working. The difference is that you are using a barButtonItem to trigger the action, whereas I am using a regular button. A barButtonItem fails, whereas a regular button succeeds. I suspect that this is due to differences in the order of message passing (what follows is conjecture, but fits with the observed behaviour):

(A) UIButton in View Controller

ViewController's button receives touchupInside
- (1) sends action to it's method
- (2) sends segue unwind action to storyboard segue
all messages received, and methods executed in this order:

actionForUnwindButton
prepareForSegue
unwindToRed

(B) UIBarButtonItem in Navigation Controller Toolbar

Tool bar buttonItem receives touchupInside
- (1) sends segue unwind action to storyboard segue
- (2) (possibly, then) sends action to viewController's method

Order of execution is

prepareForSegue
unwindToRed
actionForUnwindButton

prepareForSegue and unwind messages received. However actionForUnwindButton message is sent to nil as viewController is destroyed during the segue. So it doesn't get executed, and the log prints

prepareForSegue
unwindToRed

In the case of (B), the viewController is destroyed before the method reaches it, so does not get triggered

So it seems your options are...
(a) use a UIButton with action and unwind segue
(b) trigger your actions using prepareForSegue, which will be triggered while the viewController is still alive, and before the segue takes place.
(c) don't use an unwind segue, just use a button action. In the action method you can 'unwind' by calling popToViewController on your navigation controller.

By the way, if you implement a toolBar on the viewController (not using the navigation controller's toolbar) the result is the same: segue gets triggered first, so button action fails.

enter image description here

foundry
  • 30,849
  • 8
  • 87
  • 124
  • Thank you very much, dear foundry. I really hope that the first example as you proposed work cause it's the simplest way but it doesn't, and Im sure that the button and the action are connected. Oh, and the button I mean is actually a under tool bar item, does this matter? – Mechanic Feb 09 '15 at 10:25
  • @Mechanic, it works that way in my tests. See my update. – foundry Feb 09 '15 at 13:00
  • Dear foundry, thank you vey much for your detailed explanation. I am new to swift and iOS coding and unfortunately still I can't get it right. Would you please check my code here [link](https://github.com/xujiemechanic/UnwindSegueTest) ? – Mechanic Feb 26 '15 at 13:24
  • 1
    @Mechanic, see my update. I think you should override "prepareForSegue" instead of using a button action. You can check that it is the expected Segue using the segue identifier. _Or_ use a button action and use a 'popToViewController:' method in code, inside the button action, instead of an unwind Segue. – foundry Feb 26 '15 at 14:23
  • @foundry Can you please tell me that how can I add an unwind segue from a UIBarButton of UIToolBar ? – Ramcharan Reddy Jan 05 '17 at 14:11
3

If you are able to perform unWind Segue Successfully. Then the method in destination View Controller is called just before the segue take place, you can do what ever you want in source viewcontroller by using the segue object.

- (IBAction)unwindToThisViewController:(UIStoryboardSegue *)unwindSegue
{
  CustomViewController *vc = (CustomViewController*) unwindSegue.sourceViewController;
  [vc performAnyMethod];
  [vc saveData];
  NSString *temp = vc.anyProperty;

}

if you want your logic in source Controller then implement prepareForSegue in Scene B and set the unWind segue Identifier from Storyboard > Left View hierarchy Panel > under Exit in Scene B.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"backToSource"])
    {
        NSLog(@"Going Back");

    }
}
Irfan Gul
  • 1,530
  • 13
  • 22
  • Thank you very much, Irfan Gul, This way probably works but I feel that it's better to put these codes in the source VC. Any idea? – Mechanic Feb 09 '15 at 10:50
2

At first, you should call the send data function in prepareForSegue method.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if([[segue identifier] isEqualToString:@"UnwindIdentifier"]) {
        // send data
    }
}

If you don't want to let unwind segue happen before getting response from the server, you should override

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender

method and return NO;. Then you can perform segue manually when you get the server response by calling:

[self performSegueWithIdentifier:@"UnwindIdentifier" sender:sender];
UtkuS
  • 234
  • 3
  • 10
  • Thank you very much wrUS61, this method really works. However, why doesn't my original method work? does it mean that when a button is connected both to a unwind segue and an action then it only responses to the unwind segue? – Mechanic Feb 09 '15 at 10:43