Passing Data Between View Controllers Using Segue

Passing data between view controllers

This question seems to be very popular here on Stack Overflow so I thought I would try and give a better answer to help out people starting in the world of iOS like me.

Passing Data Forward

Passing data forward to a view controller from another view controller. You would use this method if you wanted to pass an object/value from one view controller to another view controller that you may be pushing on to a navigation stack.

For this example, we will have ViewControllerA and ViewControllerB

To pass a BOOL value from ViewControllerA to ViewControllerB we would do the following.

  1. in ViewControllerB.h create a property for the BOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. in ViewControllerA you need to tell it about ViewControllerB so use an

     #import "ViewControllerB.h"

Then where you want to load the view, for example, didSelectRowAtIndex or some IBAction, you need to set the property in ViewControllerB before you push it onto the navigation stack.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
viewControllerB.isSomethingEnabled = YES;
[self pushViewController:viewControllerB animated:YES];

This will set isSomethingEnabled in ViewControllerB to BOOL value YES.

Passing Data Forward using Segues

If you are using Storyboards you are most likely using segues and will need this procedure to pass data forward. This is similar to the above but instead of passing the data before you push the view controller, you use a method called

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

So to pass a BOOL from ViewControllerA to ViewControllerB we would do the following:

  1. in ViewControllerB.h create a property for the BOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. in ViewControllerA you need to tell it about ViewControllerB, so use an

     #import "ViewControllerB.h"
  3. Create the segue from ViewControllerA to ViewControllerB on the storyboard and give it an identifier. In this example we'll call it "showDetailSegue"

  4. Next, we need to add the method to ViewControllerA that is called when any segue is performed. Because of this we need to detect which segue was called and then do something. In our example, we will check for "showDetailSegue" and if that's performed, we will pass our BOOL value to ViewControllerB

     -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
    ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
    controller.isSomethingEnabled = YES;
    }
    }

If you have your views embedded in a navigation controller, you need to change the method above slightly to the following

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:@"showDetailSegue"]){
UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
controller.isSomethingEnabled = YES;
}
}

This will set isSomethingEnabled in ViewControllerB to BOOL value YES.

Passing Data Back

To pass data back from ViewControllerB to ViewControllerA you need to use Protocols and Delegates or Blocks, the latter can be used as a loosely coupled mechanism for callbacks.

To do this we will make ViewControllerA a delegate of ViewControllerB. This allows ViewControllerB to send a message back to ViewControllerA enabling us to send data back.

For ViewControllerA to be a delegate of ViewControllerB it must conform to ViewControllerB's protocol which we have to specify. This tells ViewControllerA which methods it must implement.

  1. In ViewControllerB.h, below the #import, but above @interface you specify the protocol.

     @class ViewControllerB;

    @protocol ViewControllerBDelegate
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
  2. Next still in the ViewControllerB.h, you need to set up a delegate property and synthesize in ViewControllerB.m

     @property (nonatomic, weak) id  delegate;
  3. In ViewControllerB we call a message on the delegate when we pop the view controller.

     NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
  4. That's it for ViewControllerB. Now in ViewControllerA.h, tell ViewControllerA to import ViewControllerB and conform to its protocol.

     #import "ViewControllerB.h"

    @interface ViewControllerA : UIViewController
  5. In ViewControllerA.m implement the following method from our protocol

     - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
    NSLog(@"This was returned from ViewControllerB %@", item);
    }
  6. Before pushing viewControllerB to navigation stack we need to tell ViewControllerB that ViewControllerA is its delegate, otherwise we will get an error.

     ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.delegate = self
    [[self navigationController] pushViewController:viewControllerB animated:YES];


References

  1. Using Delegation to Communicate With Other View Controllers in the View Controller Programming Guide
  2. Delegate Pattern

NSNotification center

It's another way to pass data.

// Add an observer in controller(s) where you want to receive data
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeepLinking:) name:@"handleDeepLinking" object:nil];

-(void) handleDeepLinking:(NSNotification *) notification {
id someObject = notification.object // Some custom object that was passed with notification fire.
}

// Post notification
id someObject;
[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

Passing Data back from one class to another (A class can be any controller, Network/session manager, UIView subclass or any other class)

Blocks are anonymous functions.

This example passes data from Controller B to Controller A

Define a block

@property void(^selectedVoucherBlock)(NSString *); // in ContollerA.h

Add block handler (listener)

Where you need a value (for example, you need your API response in ControllerA or you need ContorllerB data on A)

// In ContollerA.m

- (void)viewDidLoad {
[super viewDidLoad];
__unsafe_unretained typeof(self) weakSelf = self;
self.selectedVoucherBlock = ^(NSString *voucher) {
weakSelf->someLabel.text = voucher;
};
}

Go to Controller B

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ControllerB *vc = [storyboard instantiateViewControllerWithIdentifier:@"ControllerB"];
vc.sourceVC = self;
[self.navigationController pushViewController:vc animated:NO];

Fire block

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
NSString *voucher = vouchersArray[indexPath.row];
if (sourceVC.selectVoucherBlock) {
sourceVC.selectVoucherBlock(voucher);
}
[self.navigationController popToViewController:sourceVC animated:YES];
}

Another Working Example for Blocks

Passing Data between view Controllers Using a segue from a view embedded in a navigation controller to a tabbarcontroller

This is my view controller where you can check that I am sending 5 to tabbar first viewcontroller:

   class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.performSegue(withIdentifier: "segueIdentifier", sender: self)
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}


// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let barViewControllers = segue.destination as! UITabBarController
let destinationNv = barViewControllers.viewControllers?[0] as! UINavigationController
let destinationViewController = destinationNv.viewControllers[0] as! FirstViewController
destinationViewController.currentBalance = 5
}
}

Now You can check my firstview controller where you can check that what value we are getting.

class FirstViewController: UIViewController {

var currentBalance = 0
override func viewDidLoad() {
super.viewDidLoad()

// Do any additional setup after loading the view.
print(currentBalance)
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

}

Now, You can check my console and storyboard:
Sample Image

Sample Image

Passing data between View Controllers using Segue

"Passing data to the destination Controller" when a segue is triggered will be achieved by overriding method prepareForSegue:sender:.

Generally, you pass data and NOT the source view controller, to the destination view controller. "data" may be a certain aspect of your application "model". It's an object like "User", or perhaps an array containing "User", etc.

The destination view controller shall not have any knowledge of the source view controller. That means, the destination view controller does not need to import the header of the source view controller.

On the other hand, the source view controller may have knowledge of the concrete class of the destination view controller or a base class of the destination view controller, and thus will import the header of the destination view controller.

See: Configuring the Destination Controller When a Segue is Triggered

If you need some sort of "communication protocol" between the source and the destination, you might employ Delegation to communicate with other view controllers. This involves the definition of a @protocol (e.g. having a method doneButton) and a property delegate which is defined in the destination view controller. The protocol should be defined in the header of the destination view controller, if it's specific to the destination view controller. Usually, you define tho protocol from the view point of the destination controller, and not from the requirements of the source controller.

The source view controller then creates a delegate (unless, it itself is it already) and sets the delegate of of the destination view controller. The destination view controller will send the delegate methods to the delegate, and the delegate handles it.

Now, passing "data" from a VC_A to VC_B should be straight forward. You should read a few examples which use prepareForSegue:sender:. For example, the destination view controller may have a property data which represents the thing it should display. The source view controller must set this property in prepareForSegue:sender:.

Passing data from VC_A over VC_B to VC_C should be straight forward as well.

Note: Each view controller may tailor (separate, modify, prepare, slice, transform, etc.) its data to make it a suitable data for the next view controller.


If VC_C needs data that is not available in its source view controller VC_B, then there are a couple of approaches to solve this. However, this is usually a sign of bad design.

You could have an application model, which is global. Suppose, your "application model" is an object of type Document. Suppose, at any time there is only one instance of that application model. Then, the model is a "Singleton", which could be accessed from anywhere in your app like so:

Document* document = [Document sharedDocument];

However, the preferred way to get an instance of a model is in the first view controller which requires access to it, in this case: VC_A.

Then, VC_A passes a Document instance to the next view controller VC_B. And VC_B passes the document object to VC_C.

You should read the official documentation "View Controller Programming Guide for iOS".


Example 1

Suppose, you have a list "Users". The list should be displayed in a table view controller, and there should also be a detail view, showing details of one User.

The table view controller will have a "data" property users:

In UsersTableViewController.h:

@interface UsersTableViewController : UIViewController
@property (nonatomic, readonly) NSArray* users;
@end

(Strictly, this user property doesn't need to be public. For example, if the table view internally obtains the list of users itself, there is not need to access it from outside.

The "users" array is the data of the table view which shall be displayed in rows. Each row shows a "summary" of a user.

More details of a user should be displayed in a detail view controller. The detail view controller's data is a single user of type User.

When the user tabs a certain row in the table view, the detail view controller will be displayed. Before it will be displayed, the table view controller must configure the detail view controller: the table view controller assigns the detail view controller's "data property" the current selected user. Thus, the detail view controller should have a public property user:

@interface UserViewController : UIViewController
@property (nonatomic) User* user;
@end

The table view controller configures the detail view controller in prepareForSegue:sender::

In UsersTableViewController.m

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"ShowUserDetails"]) {
UserViewController* userViewController = [segue destinationViewController];
userViewController.user = [self.users objectInListAtIndex:[self.tableView indexPathForSelectedRow].row];
}
}

Example 2

The second example is more complex and uses "Delegation" as a means to establish a communication between controllers.

Caution:

This is not a complete example. The purpose of this example shall demonstrate how to use "Delegation". A full featured implementation of data tasks as shown in the example will require significant more effort. In this scenarios like these, "Delegation" will be the most preferred approach to accomplish this (IMHO).

Suppose we want to

  • Show a User
  • Modify (edit) a User
  • Create New a User, and
  • Delete a User

from within the detail view.

These "data tasks" shall not be performed by the detail view controller itself, instead a delegate is responsible for these data tasks.

These data actions shall be handled by the delegate:

@protocol UserDataSourceDelegateProtocol 
- (User*) viewControllerUser:(UserViewControllerBase*)viewController;
- (void) viewController:(UserViewControllerBase*)viewController dismissWithUpdatedUser:(User*)user;
- (void) viewController:(UserViewControllerBase*)viewController dismissWithDeletedUser:(User*)user;
- (void) viewController:(UserViewControllerBase*)viewController dismissWithCreatedUser:(User*)user;
@end

This protocol reflects the basic CRUD methods (Create, Read, Update, Delete).

Again, we don't want the Detail View Controller itself to perform these data methods, but instead this will be performed by an instance implementing the UserDataSourceDelegateProtocol. The detail view controller has a property of this delegate, and it sends these "data tasks" to the delegate.

There may be several detail view controllers, all subclasses of abstract class UserViewControllerBase which handle the show, edit and create tasks. Deletion of a user can be performed in the table view and in the "Show User" view controller:

  • ShowUserViewController
  • EditUserViewController
  • NewUserViewController

For example The EditUserViewController will send viewController:dismissWithUpdatedUser: when the user tabs the "back" button AND if the user has modified the user object. Now, the delegate may or may not allow to dismiss the detail view. It may disallow it when there are validation errors for example.

The UserDataSourceDelegateProtocol protocol may be implemented in the root view controller, the table view controller for example. However, a separate class whose sole responsibility is to handle data tasks may be more appropriate. In the sample below, the table view controller will also be this data handler.

The UserDataSourceDelegateProtocol may be defined in an extra header.

In UsersTableViewController.m:

#import "UserDataSourceDelegateProtocol.h"
#import "ShowUserViewController.h"


@interface UsersTableViewController ()
@property (nonatomic, readonly) NSArray* users;
@end


// This delegate will be called when the detail view controller request
// the user object which shall be displayed.
- (User*) viewControllerUser:(UserViewControllerBase*)viewController {
return [self.users objectInListAtIndex:[self.tableView indexPathForSelectedRow].row];
}

Here, the Table View Controller configures the Show User Detail View Controller:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:UserShowSegueID])
{
ShowUserViewController* showViewController = segue.destinationViewController;
showViewController.delegate = self; // Data Source Handler is self
}
}

The "Edit User" view controller is usually a destination view controller of the "Show User" view controller, which gets viewed when the user tabs the "Edit" button.

The "Show User" view controller will setup the delegate for the
"Edit User" view controller gets the same delegate:

In ShowUserViewController.m

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:UserEditSegueID])
{
EditUserViewController* editViewController = segue.destinationViewController;
editViewController.delegate = self.delegate; // pass through the data source hanlder
}
}

The data delegate may handle an updated user as follows:

In UsersTableViewController.m:

- (void) viewController:(UserViewControllerBase*)viewController
dismissWithUpdatedUser:(User*)user {
if (/* is valid user and can be saved */) {
[viewController.presentingViewController dismissViewControllerAnimated:YES
completion:nil];
}
}

How to transfer data between view controller using segues

As suggested by @Sweeper, it could very well be that your destination view controller is embedded in a UINavigationViewController, hence your segue.destination is in fact a UINavigationViewController, not a SecondVC.

You can try this code:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var destinationViewController = segue.destination
if let navigationController = destinationViewController as? UINavigationController {
destinationViewController = navigationController.visibleViewController ?? destinationViewController
}
if let secondController = destinationViewController as? SecondVC {
secondController?.name = first
}
}

Of course the first four lines of code could be refactored in an appropriate function (even better if in an extension of UIViewController).

If that solves the problem, you can watch cs193p Stanford iOS course for further details.
In particular watch https://www.youtube.com/watch?v=HQrXM2zUPvY&index=6&list=PLPA-ayBrweUz32NSgNZdl0_QISw-f12Ai starting from the 30:20 mark.

How to pass data between view controller and the tab-bar's view controllers using segue?

I got my answer

    @IBAction func submitButonTapped(_ sender: Any) {
performSegue(withIdentifier: "homePage", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let barViewControllers = segue.destination as! UITabBarController
let destinationViewController = barViewControllers.viewControllers![0] as! ViewControllerB
destinationViewController.containsData = Data
}

Passing Data Between View Controllers in Swift 4

Your problem is that your prepare methods never run because you never call them.

Look, when you call performSegue, then prepare(for segue: sender:) is called too, so you can override this method in your ViewController and because you're passing identifier as parameter of performSegue method you can determine what should happen if segue has this or this identifier

So, delete prepare for segue methods from IBActions

@IBAction func backButton(_ sender: Any) {
if installer == "verified"{
performSegue(withIdentifier: "main/login", sender: self)
}
}

@IBAction func button1(_ sender: Any) {
if installer == "verified"{
performSegue(withIdentifier: "Button1", sender: self)
}
}

instead override prepare(for segue: sender:) method of ViewController and inside specify what should happen if segue has "main/login" indentifier or "Button1":

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "main/login" {
let mainController = segue.destination as! ViewController
mainController.myvar = installer
} else if segue.identifier == "Button1"
let IPController = segue.destination as! IP_ModuleQuickStartViewController
IPController.verified = installer
}
}

Can't pass data between view controllers using segues (Xcode 7) (Swift)

As stated on the comment the problem was that you were using a push segue on a UIViewController.

Please note that push and modal segues are deprecated... Please take a look at this post entry and let me know if you have any doubts

What's the difference between all the Selection Segues?

How to pass data between view controllers - not working

This is an inefficient way to do this as you are passing data through 3 different objects. However, going on with this methodology, the problem is the label's are not created yet inside

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {

VC.player1ScoreLabel.text = String(player1CurrentScore)
VC.player2ScoreLabel.text = String(player2CurrentScore)
VC.player3ScoreLabel.text = String(player3CurrentScore)
VC.player4ScoreLabel.text = String(player4CurrentScore)

}
}

See, the label isn't created yet. So, you are setting text on aUILabel that isn't initialized. Therefore, you need to create variables for the labels inside ThirdViewController.

Third View Controller

class ThirdViewController: UIViewController {

@IBOutlet var player1ScoreLabel: UILabel!
@IBOutlet var player2ScoreLabel: UILabel!
@IBOutlet var player3ScoreLabel: UILabel!
@IBOutlet var player4ScoreLabel: UILabel!

var score0:Int!
var score1:Int!
var score2:Int!
var score3:Int!

override func viewDidLoad() {
super.viewDidLoad()
self.player1ScoreLabel.text = String(score0)
self.player2ScoreLabel.text = String(score1)
self.player3ScoreLabel.text = String(score2)
self.player4ScoreLabel.text = String(score3)
}
}

and change the segue in SecondViewController

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {

VC.score0 = player1CurrentScore
VC.score1 = player2CurrentScore
VC.score2 = player3CurrentScore
VC.score3 = player4CurrentScore

}
}

Another Way:

Let's create a singleton called Game. Within this (assuming you only have 4 players) we can create 4 players that will never change. This allows us to create instances of players in one location and call upon them as necessary.

NOTE: A singleton can be misused EASILY.

https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift
https://cocoacasts.com/are-singletons-bad/

class Game {

static var score0:Int = 0
static var score1:Int = 0
static var score2:Int = 0
static var score2:Int = 0

}

Then, anywhere in your code you can access Game.score0, Game.score1.

CAUTION:

I would caution you to very carefully use singletons. You don't want everything with public access. You need to determine if this is good for you or not. Cheers.



Related Topics



Leave a reply



Submit