360

I have many annotations in a mapview (with rightCalloutAccessory buttons). The button will perform a segue from this mapview to a tableview. I want to pass the tableview a different object (that holds data) depending on which callout button was clicked.

For example: (totally made up)

  • annotation1 (Austin) -> pass data obj 1 (relevant to Austin)
  • annotation2 (Dallas) -> pass data obj 2 (relevant to Dallas)
  • annotation3 (Houston) -> pass data obj 3 and so on... (you get the idea)

I am able to detect which callout button was clicked.

I'm using prepareForSegue: to pass the data obj to the destination ViewController. Since I cannot make this call take an extra argument for the data obj I require, what are some elegant ways to achieve the same effect (dynamic data obj)?

Any tip would be appreciated.

Grender
  • 1,589
  • 2
  • 17
  • 44
chizzle
  • 4,142
  • 5
  • 18
  • 18
  • possible duplicate of [Pass variables from one ViewController to another in Swift](http://stackoverflow.com/questions/24044108/pass-variables-from-one-viewcontroller-to-another-in-swift) – Paulw11 Oct 05 '14 at 23:05

10 Answers10

673

Simply grab a reference to the target view controller in prepareForSegue: method and pass any objects you need to there. Here's an example...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Make sure your segue name in storyboard is the same as this line
    if ([[segue identifier] isEqualToString:@"YOUR_SEGUE_NAME_HERE"])
    {
        // Get reference to the destination view controller
        YourViewController *vc = [segue destinationViewController];

        // Pass any objects to the view controller here, like...
        [vc setMyObjectHere:object];
    }
}

REVISION: You can also use performSegueWithIdentifier:sender: method to activate the transition to a new view based on a selection or button press.

For instance, consider I had two view controllers. The first contains three buttons and the second needs to know which of those buttons has been pressed before the transition. You could wire the buttons up to an IBAction in your code which uses performSegueWithIdentifier: method, like this...

// When any of my buttons are pressed, push the next view
- (IBAction)buttonPressed:(id)sender
{
    [self performSegueWithIdentifier:@"MySegue" sender:sender];
}

// This will get called too before the view appears
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"MySegue"]) {

        // Get destination view
        SecondView *vc = [segue destinationViewController];

        // Get button tag number (or do whatever you need to do here, based on your object
        NSInteger tagIndex = [(UIButton *)sender tag];

        // Pass the information to your destination view
        [vc setSelectedButton:tagIndex];
    }
}

EDIT: The demo application I originally attached is now six years old, so I've removed it to avoid any confusion.

Simon
  • 8,981
  • 2
  • 26
  • 32
  • 1
    Thank you but I want to set `[vc setMyObjectHere:object];` this dynamically. i.e. obj1 for button1, obj2 for button2 The trouble is I can't pass an argument in. Is there a way around this? – chizzle Oct 23 '11 at 08:25
  • 2
    I've updated my post with a downloadable example of what I'm talking about. – Simon Oct 23 '11 at 14:34
  • that worked! Thanks a bunch. As a side note prepareForSegue: has a UIControl argument which is a parent class of UIButton (thus able to get the tag) :D – chizzle Oct 23 '11 at 18:28
  • Does prepareForSegue get called even when your just pushing to a navigation controller without a storyboard segue? – zakdances Jun 10 '12 at 20:08
  • This is one of the most thorough answers I've ever seen. Good code samples, solves the issue, offers downloadable sample... wow. I'm impressed! – lewiguez Jun 17 '12 at 03:14
  • Is there a reason why [super prepareForSegue] is not included every time we override prepareForSegue? – pnizzle May 24 '13 at 06:16
  • nizz .. right. Here's a good example of that freaking "default .. super" code ... http://stackoverflow.com/a/25474548/294884 Hope it helps someone. Pls vote that up since only a few (el33t) people even look at questions about segues! :-) – Fattie Sep 01 '14 at 06:47
  • Just noticed the download link was no longer working. It's fixed now. Keep in mind, this is for a much older version of iOS now. – Simon Dec 18 '14 at 07:47
  • *Caution!* Don't try to access UI components at this stage since they'll not be ready allocated yet. – Mohammad Abdurraafay Mar 30 '17 at 10:16
82

Sometimes it is helpful to avoid creating a compile-time dependency between two view controllers. Here's how you can do it without caring about the type of the destination view controller:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController respondsToSelector:@selector(setMyData:)]) {
        [segue.destinationViewController performSelector:@selector(setMyData:) 
                                              withObject:myData];
    } 
}

So as long as your destination view controller declares a public property, e.g.:

@property (nonatomic, strong) MyData *myData;

you can set this property in the previous view controller as I described above.

TotoroTotoro
  • 17,524
  • 4
  • 45
  • 76
  • 12
    This is really a matter of opinion. I've not (yet) had a situation where I didn't want to control the view controllers 'tightly', though I do appreciate what you're saying and it may be important in the right circumstances. – Simon Jul 17 '12 at 14:15
  • 2
    @Simon: yeah, you just choose the approach that's best for you. In the app I'm working on now, for example, my approach makes a lot of sense since I keep adding view controllers that need the same data object. Being able to hook them up with just a segue and knowing that they will get the right data is super convenient. – TotoroTotoro Jul 17 '12 at 23:41
  • 10
    It's not a matter of opinion, it's just wrong :) The phrase "The accepted answer is not the best way of doing this" is wrong. It should read "In certain cases, you need to do this..." – Fattie Jun 22 '14 at 13:22
  • 2
    I prefer Simon's method. As it will tell me the errors at compile time. For example, if I missed the declaration of myData in the destination view controller, I will know immediately. But for your scenario, your approach seems good! – Abdurrahman Mubeen Ali Sep 10 '14 at 13:50
  • @AbdurrahmanMubeenAli Unit tests should take care of your missing declarations, not unnecessary dependencies. – gchbib Dec 10 '14 at 16:28
  • 15
    This approach absolutely does create a dependency, as you're requiring the destination view controller to have a `setMyData:`. That is a dependency. The fact that you use a selector to avoid the compile error should be treated as a *weakness* of your approach, not a benefit. It's shocking to me how many developers have lost the concept that compile time errors should be preferred to run-time errors. – Nate Jan 25 '15 at 23:59
  • Now, if there's no such one-to-many relationship between the VCs, and two of them are logically coupled, then yes, it's better to have an explicit dependency between them, which can be checked at compile time -- like you suggest. – TotoroTotoro Jan 26 '15 at 03:28
23

In Swift 4.2 I would do something like that:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let yourVC = segue.destination as? YourViewController {
        yourVC.yourData = self.someData
    }
}
Remy Cilia
  • 2,573
  • 1
  • 20
  • 31
16

I have a sender class, like this

@class MyEntry;

@interface MySenderEntry : NSObject
@property (strong, nonatomic) MyEntry *entry;
@end

@implementation MySenderEntry
@end

I use this sender class for passing objects to prepareForSeque:sender:

-(void)didSelectItemAtIndexPath:(NSIndexPath*)indexPath
{
    MySenderEntry *sender = [MySenderEntry new];
    sender.entry = [_entries objectAtIndex:indexPath.row];
    [self performSegueWithIdentifier:SEGUE_IDENTIFIER_SHOW_ENTRY sender:sender];
}

-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_SHOW_ENTRY]) {
        NSAssert([sender isKindOfClass:[MySenderEntry class]], @"MySenderEntry");
        MySenderEntry *senderEntry = (MySenderEntry*)sender;
        MyEntry *entry = senderEntry.entry;
        NSParameterAssert(entry);

        [segue destinationViewController].delegate = self;
        [segue destinationViewController].entry = entry;
        return;
    }

    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_HISTORY]) {
        // ...
        return;
    }

    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_FAVORITE]) {
        // ...
        return;
    }
}
neoneye
  • 50,398
  • 25
  • 166
  • 151
  • are we *using* `prepareForSegue` or we are *implementing* it and the assumption is prepareForSegue gets called itself ie we **don't** need to do something like `[self prepareForSegue]` – mfaani Sep 18 '16 at 13:36
16

I came across this question when I was trying to learn how to pass data from one View Controller to another. I need something visual to help me learn though, so this answer is a supplement to the others already here. It is a little more general than the original question but it can be adapted to work.

This basic example works like this:

enter image description here

The idea is to pass a string from the text field in the First View Controller to the label in the Second View Controller.

First View Controller

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        // get a reference to the second view controller
        let secondViewController = segue.destinationViewController as! SecondViewController

        // set a variable in the second view controller with the String to pass
        secondViewController.receivedString = textField.text!
    }

}

Second View Controller

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    // This variable will hold the data being passed from the First View Controller
    var receivedString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Used the text from the First View Controller to set the label
        label.text = receivedString
    }

}

Remember to

  • Make the segue by control clicking on the button and draging it over to the Second View Controller.
  • Hook up the outlets for the UITextField and the UILabel.
  • Set the first and second View Controllers to the appropriate Swift files in IB.

Source

How to send data through segue (swift) (YouTube tutorial)

See also

View Controllers: Passing data forward and passing data back (fuller answer)

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
6

For Swift use this,

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var segueID = segue.identifier

    if(segueID! == "yourSegueName"){

        var yourVC:YourViewController = segue.destinationViewController as YourViewController

        yourVC.objectOnYourVC = setObjectValueHere!

    }
}
Mohammad Zaid Pathan
  • 16,304
  • 7
  • 99
  • 130
4

I've implemented a library with a category on UIViewController that simplifies this operation. Basically, you set the parameters you want to pass over in a NSDictionary associated to the UI item that is performing the segue. It works with manual segues too.

For example, you can do

[self performSegueWithIdentifier:@"yourIdentifier" parameters:@{@"customParam1":customValue1, @"customValue2":customValue2}];

for a manual segue or create a button with a segue and use

[button setSegueParameters:@{@"customParam1":customValue1, @"customValue2":customValue2}];

If destination view controller is not key-value coding compliant for a key, nothing happens. It works with key-values too (useful for unwind segues). Check it out here https://github.com/stefanomondino/SMQuickSegue

Stefano Mondino
  • 1,219
  • 11
  • 15
2

My solution is similar.

// In destination class: 
var AddressString:String = String()

// In segue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if (segue.identifier == "seguetobiddetailpagefromleadbidder")
    {
        let secondViewController = segue.destinationViewController as! BidDetailPage
        secondViewController.AddressString = pr.address as String
    }
}
Community
  • 1
  • 1
Alvin George
  • 14,148
  • 92
  • 64
1

Just use this function.

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let index = CategorytableView.indexPathForSelectedRow
    let indexNumber = index?.row
    let VC = segue.destination as! DestinationViewController
   VC.value = self.data

}
Parth Barot
  • 355
  • 3
  • 9
0

I used this solution so that I could keep the invocation of the segue and the data communication within the same function:

private var segueCompletion : ((UIStoryboardSegue, Any?) -> Void)?

func performSegue(withIdentifier identifier: String, sender: Any?, completion: @escaping (UIStoryboardSegue, Any?) -> Void) {
    self.segueCompletion = completion;
    self.performSegue(withIdentifier: identifier, sender: sender);
    self.segueCompletion = nil
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    self.segueCompletion?(segue, sender)
}

A use case would be something like:

func showData(id : Int){
    someService.loadSomeData(id: id) {
        data in
        self.performSegue(withIdentifier: "showData", sender: self) {
            storyboard, sender in
            let dataView = storyboard.destination as! DataView
            dataView.data = data
        }
    }
}

This seems to work for me, however, I'm not 100% sure that the perform and prepare functions are always executed on the same thread.

dannrob
  • 1,061
  • 9
  • 10