iOS: How To Pass Data with an Unwind Segue

Yesterday, I got really excited after reading this great tutorial on how to unwind a segue. I knew there was a way to unwind a segue, but I never quite got how to use it, so I just used the standard dismissViewController code to go back to the initial view controller instead.

However, the part that excited me most about this was not just doing a standard segue unwind, but the realization of how to pass information back to the original view controller using the unwind segue!

Consider this simple application:

When I select a color from the ColorsViewConroller, the color gets passed back to the main view controller, where it is set as the main view controller’s background color. To do this simple task in the past, I would use either the delegate, block, or notification pattern.

Here is how to accomplish this same task using the unwind segue:

The Setup

I created a very simple application:

iOS-Storyboard

The NTRMainViewController has a button “Change Color” with triggers a segue to bring up the NTRColorsTableViewConroller using the modal animation. The NTRColorsTableViewConroller is configured with a “colorCell”, which just changes a background color. The NTRColorsTableViewController’s initial table view configuration is as follows:

@interface NTRColorsTableViewController ()

@property (strong, nonatomic) NSArray *colors;

@end

@implementation NTRColorsTableViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.colors count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"colorCell"; forIndexPath:indexPath];

    cell.contentView.backgroundColor = self.colors[indexPath.row];
    
    return cell;
}


#pragma mark - Setter / Getter Overrides

- (NSArray *)colors
{
    if (!_colors) {
        self.colors = @[[UIColor blueColor],
                        [UIColor lightGrayColor],
                        [UIColor cyanColor],
                        [UIColor yellowColor],
                        [UIColor magentaColor],
                        [UIColor purpleColor],
                        [UIColor brownColor],
                        [UIColor redColor],
                        [UIColor grayColor],
                        [UIColor whiteColor],
                        [UIColor orangeColor]];
    }
    return _colors;
}

@end

Create an Unwind Action

The key with the unwind segue is that you need to create the unwind action in the view controller you want to unwind to! In my case, I want to unwind from the NTRColorsTableViewController to the NTRMainViewConroller. So I need to create the unwind action in the NTRMainViewConroller:

// NTRMainViewConroller.m
- (IBAction)unwindFromModalViewController:(UIStoryboardSegue *)segue
{

}

Connect to the Unwind Action

And just like with other IBActions, we need to connect to it in the storyboard. I’m going to start by triggering the unwind from the NTRColorsTableViewConroller’s Cancel button. Here’s how:

  • Control drag the cancel button to the Exit sign
  • Select the previously specified unwind action (see above section)
  • Add an identifier to the unwind segue
  •  

    Trigger the Segue

    The unwind segue now acts as any other segue! It will automatically be triggered when the Cancel button is clicked in the NTRColorsTableViewController. However, you also want to trigger it in the tableView:didSelectRowAtIndexPath method, when the user selects a color cell:

    //NTRColorsTableViewController.m
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        self.selectedColor = self.colors[indexPath.row];
        [self performSegueWithIdentifier:@"colorSelected" sender:self];
    }
    

    Pass the Data!

    Finally, you are ready to pass the color from the NTRColorsTableViewController to the NTRMainViewConroller! There are two ways to do this:

    Unwind Action

    Remember, the unwind action passes in a the segue, so you now have access to the source view controller – which is the color view controller in our case. You can now fill in your unwind action with the necessary code:

    //NTRMainViewConroller.m
    - (IBAction)unwindFromModalViewController:(UIStoryboardSegue *)segue
    {
        if ([segue.sourceViewController isKindOfClass:[NTRColorsTableViewController class]]) {
            NTRColorsTableViewController *colorsViewConroller = segue.sourceViewController;
            // if the user clicked Cancel, we don't want to change the color
            if (colorsViewConroller.selectedColor) {
                self.view.backgroundColor = colorsViewConroller.selectedColor;
            }
        }
    }
    

    Prepare for Segue

    Again, the unwind segue is still a segue. So just like any other segue, you can take advantage of the prepareForSegue:sender method in your view controller.

    //NTRMainViewConroller.h
    @interface NTRMainViewController : UIViewController
    
    @property (strong, nonatomic) UIColor *backgroundColor;
    
    @end
    
    //NTRMainViewConroller.m
    
    // other methods
    
    #pragma mark - Setter Overrides
    
    - (void)setBackgroundColor:(UIColor *)backgroundColor
    {
        _backgroundColor = backgroundColor;
        self.view.backgroundColor = backgroundColor;
    }
    
    
    //NTRColorsTableViewConroller.m
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([segue.destinationViewController isKindOfClass:[NTRMainViewController class]]) {
            NTRMainViewController *mainViewConroller = segue.destinationViewController;
            if (self.selectedColor) {
                mainViewConroller.backgroundColor = self.selectedColor;
            }
        }
    }
    

    You can view the full example source code on Github. Happy Unwinding!

    Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

    • Motif

      Have been looking for something like this for hours! Awesome job explaining. Thank you!

    • Tomblasta

      Wow Thanks so much! I have been looking for a tutorial like this for days. I converted it to swift in my project and it worked like a charm. Great job explaining how everything works. Thanks!

    • Hok Shun Poon

      So sad that unwind segues, as with all segues, don’t carry any data in and of themselves… it’s for one ViewController to know about another one for data to be passed between them. This increases coupling!

      • Benjohn

        🙂 I’m amazingly happy I’m not the only one to notice the coupling issue.

        Either the segue instance having user data or remembering sender, or the unwind handler having an additional parameter for picking up associated data would work well.

        I came and read this post hoping someone had found a way of doing this. Alas, no. Still – it is an improvement over segue free approaches, imho.

        • Mike

          You can potentially do this with a custom UIStoryboardSegue subclass, however, this seems to crash for unwind segues pre iOS 9.

          You also still need to cast the segue to your subclass, But this would reduce coupling between view controllers.

        • SergeyCHiP

          I think you can use -canPerformUnwindSegueAction:fromViewController:withSender: in destination vc
          and use this “sender” to pass data.

          • Benjohn Barnes

            That’s not ideal because that unwind _might not happen_ (right ?). It’s canUnwind… – it’s a question to check. It’s not willUnwindDefinitelyForSure.

            So instead of making a change to the receiving view controller which is immediate and atomic, you need to cache the details of a change that you _might_ make in future _if_ the segue does actually happen to you. Which means that you’ve got needless mutable state hanging about. That state could get changed between canPerform… and you actually doing the perform (eek!); you should remember to tear down the state in the perform (needless retains of stuff waste memory); and that kind of stuff that I need to keep hold of, that’s not really part of my state, but is needed by some of my functionality, is exactly the kind of thing that will be misunderstood by a future coder (probably you or me) that wonders what all this stuff is for because it’s not local to where it’s being used.

            Phew!

            What you want id for the damn data payload associated with the unwind to be available at the unwind handler where the unwind is processed. Either as a parameter, or as something in the segue.

    • safa

      thank you

    • wLcDesigns

      Thank you! I also converted this to swift and it works great.

    • Stan92

      Hi,

      Thanks for your tuto..

      I have a little problem. If I select an item from a UISearchController, it seems the
      “[self performSegueWithIdentifier….” doesn’t not work from the “didSelectRowAtIndexPath” method..
      In fact, it just closes the UISearchController..
      Do I miss something?
      Any help?

    • Paul

      Awesome, thank you! This is exactly what I needed.

    • Doug Voss

      Thanks natasha the robot!

    • Kastor

      Thanks !

    • Bojan Ursus

      thanks!!! i’ve been struggling for 4 hrs, it turned out i was adding unwind… in wrong controller.m!