By chizzle


2011-10-23 04:34:43 8 Comments

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.

10 comments

@Macondo2Seattle 2012-06-01 23:44:46

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.

@Simon 2012-07-17 14:15:28

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.

@Macondo2Seattle 2012-07-17 23:41:24

@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.

@Fattie 2014-06-22 13:22:41

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..."

@Abdurrahman Mubeen Ali 2014-09-10 13:50:21

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!

@thgc 2014-12-10 16:28:18

@AbdurrahmanMubeenAli Unit tests should take care of your missing declarations, not unnecessary dependencies.

@Nate 2015-01-25 23:59:43

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.

@Macondo2Seattle 2015-01-26 03:28:18

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.

@Remy Cilia 2015-04-09 23:22:49

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
    }
}

@Andrew K 2017-01-29 00:12:12

Do you need to call : ` super.prepare(for: segue, sender: sender)`?

@Parth Barot 2018-04-12 12:40:05

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

}

@Simon 2011-10-23 08:16:35

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.

@chizzle 2011-10-23 08:25:55

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?

@Simon 2011-10-23 14:34:31

I've updated my post with a downloadable example of what I'm talking about.

@chizzle 2011-10-23 18:28:55

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

@zakdances 2012-06-10 20:08:04

Does prepareForSegue get called even when your just pushing to a navigation controller without a storyboard segue?

@lewiguez 2012-06-17 03:14:44

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!

@pnizzle 2013-05-24 06:16:50

Is there a reason why [super prepareForSegue] is not included every time we override prepareForSegue?

@Fattie 2014-09-01 06:47:38

nizz .. right. Here's a good example of that freaking "default .. super" code ... 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! :-)

@Simon 2014-12-18 07:47:59

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.

@Mohammad Abdurraafay 2017-03-30 10:16:32

Caution! Don't try to access UI components at this stage since they'll not be ready allocated yet.

@dannrob 2016-11-22 03:23:15

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.

@A.G 2015-09-28 12:43:02

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
    }
}

@Suragch 2015-08-11 06:18:30

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)

@Zaid Pathan 2015-03-27 11:45:53

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!

    }
}

@Stefano Mondino 2014-02-21 11:26:30

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

@neoneye 2012-12-08 14:02:07

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;
    }
}

@Honey 2016-09-18 13:36:47

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]

Related Questions

Sponsored Content

22 Answered Questions

[SOLVED] How do I check if a string contains another string in Objective-C?

36 Answered Questions

[SOLVED] How can I disable the UITableView selection?

41 Answered Questions

[SOLVED] Passing Data between View Controllers

25 Answered Questions

[SOLVED] How do I sort an NSMutableArray with custom objects in it?

9 Answered Questions

[SOLVED] How to "add existing frameworks" in Xcode 4?

1 Answered Questions

Pass data depends on the button in tableView cell

1 Answered Questions

1 Answered Questions

[SOLVED] how to access segue in 'didSelectRowAtIndexPath' - Swift/IOS

2 Answered Questions

[SOLVED] prepareforsegue vs didselectrowatindexpath

2 Answered Questions

Sponsored Content