92

Say I have multiple view controllers in my Swift app and I want to be able to pass data between them. If I'm several levels down in a view controller stack, how do I pass data to another view controller? Or between tabs in a tab bar view controller?

(Note, this question is a "ringer".) It gets asked so much that I decided to write a tutorial on the subject. See my answer below.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 1
    Try googling for delegates – milo526 Apr 19 '15 at 19:45
  • 5
    I posted this so that I could provide a solution to the 10,000 instances of this question that show up every day here on SO. See my self-answer. :) – Duncan C Apr 19 '15 at 19:47
  • Sorry I was too quick with reacting :) good to be able to link to this :) – milo526 Apr 19 '15 at 19:48
  • 2
    No worries. You thought I was # 10,001, didn't you? – Duncan C Apr 19 '15 at 19:51
  • 4
    @DuncanC I don't like your answer. :( It's okay-isn as a catch-all every scenario answer... insomuchas, it will *work* for every scenario, but it's also not the *right* approach for almost *any* scenario. Despite this, we've now got it into our head that marking any question on the topic as a duplicate of this one is a good idea? Please, don't. – nhgrif Apr 20 '15 at 00:44
  • @nhgrif, this thread now includes several answers outlining different approaches. Your point that a data container singleton is not always the best answer is well taken, and I've modified my first answer to the question to reflect that. I think that overall the whole thread, with your answer included, is a decent overview of different ways of solving the problem. – Duncan C May 12 '15 at 00:08
  • Thanks for adding this Q/A. Very valuable. It could be made more valuable, I think, if you and @nhgrif were to distinguish the approaches you suggest in terms of why one might opt for one or the other. I'd distinguish them loosely in terms of *what* is being communicated between VCs, characterizing your excellent answer as VCs communicating *model* info via a shared model, and nhgrif's equally useful contribution as sharing *view-controller* info (say, a selection) via direct messaging. – danh May 12 '15 at 02:40

9 Answers9

91

Your question is very broad. To suggest there is one simple catch-all solution to every scenario is a little naïve. So, let's go through some of these scenarios.


The most common scenario asked about on Stack Overflow in my experience is the simple passing information from one view controller to the next.

If we're using storyboard, our first view controller can override prepareForSegue, which is exactly what it's there for. A UIStoryboardSegue object is passed in when this method is called, and it contains a reference to our destination view controller. Here, we can set the values we want to pass.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "MySegueID" {
        if let destination = segue.destination as? SecondController {
            destination.myInformation = self.myInformation
        }
    }
}

Alternatively, if we're not using storyboards, then we're loading our view controller from a nib. Our code is slightly simpler then.

func showNextController() {
    let destination = SecondController(nibName: "SecondController", bundle: nil)
    destination.myInformation = self.myInformation
    show(destination, sender: self)
}

In both cases, myInformation is a property on each view controller holding whatever data needs to be passed from one view controller to the next. They obviously don't have to have the same name on each controller.


We might also want to share information between tabs in a UITabBarController.

In this case, it's actually potentially even simpler.

First, let's create a subclass of UITabBarController, and give it properties for whatever information we want to share between the various tabs:

class MyCustomTabController: UITabBarController {
    var myInformation: [String: AnyObject]?
}

Now, if we're building our app from the storyboard, we simply change our tab bar controller's class from the default UITabBarController to MyCustomTabController. If we're not using a storyboard, we simply instantiate an instance of this custom class rather than the default UITabBarController class and add our view controller to this.

Now, all of our view controllers within the tab bar controller can access this property as such:

if let tbc = self.tabBarController as? MyCustomTabController {
    // do something with tbc.myInformation
}

And by subclassing UINavigationController in the same way, we can take the same approach to share data across an entire navigation stack:

if let nc = self.navigationController as? MyCustomNavController {
    // do something with nc.myInformation
}

There are several other scenarios. By no means does this answer cover all of them.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
nhgrif
  • 61,578
  • 25
  • 134
  • 173
  • 1
    I'd also add that sometimes you want a channel to send information back from the destination view controller to the source view controller. A common way to handle that situation is to add a delegate property to the destination, and then in the source view controller's prepareForSegue, set the destination view controller's delegate property to self. (and define a protocol that defines the messages the destination VC uses to send messages to the source VC) – Duncan C Apr 20 '15 at 02:27
  • 1
    nhgrif, I agree. The counsel to new developers should be that if you need to pass data between scenes on storyboard, use `prepareForSegue`. It's too bad that this very simple observation is lost amongst the other answers and digressions here. – Rob Jun 19 '15 at 15:25
  • 2
    @Rob Yup. Singletons and notifications should be last choices. We should prefer `prepareForSegue` or other **direct** transfers of information in almost every scenario and then simply be okay with the novices when they show up with the scenario for which these situations don't work and we then have to teach them about these more global approaches. – nhgrif Jun 19 '15 at 15:41
  • I don't think the subclassing of the Tab Bar Controller is a good recommendation. Instead, in the App Delegate, walk through the Tab Bar Controller's child view controllers and inject whatever the "Data Object" is (preferably a managed object context / custom "core data controller") into them. – bpapa Mar 26 '16 at 13:31
  • 1
    It depends. But I'm very, very concerned about using the app delegate as our dumping ground for code we don't know where else to put. Here lies the path to madness. – nhgrif Mar 26 '16 at 13:40
  • 2
    @nhgrif. thx for your answer. what if however you want the data to be passed between say 4 or 5 viewcontrollers. if ive got say 4-5 viewcontrollers managing client login and password etc and i want to pass the user' email between these viewcontrollers, is there a more convenient way to do this than declaring the var in each viewcontroller then passing it within prepareforsegue. is there a way i can declare once and each viewcontroller can access it but in a way that is also good coding practice? – lozflan Jun 05 '16 at 22:42
47

This question comes up all the time.

One suggestion is to create a data container singleton: An object that gets created once and only once in the life of your application, and persists for the life of your app.

This approach is well suited for a situation when you have global app data that needs to be available/modifiable across different classes in your app.

Other approaches like setting up one-way or 2-way links between view controllers are better suited to situations where you are passing information/messages directly between view controllers.

(See nhgrif's answer, below, for other alternatives.)

With a data container singleton, you add a property to your class that stores a reference to your singleton, and then use that property any time you need access.

You can set up your singleton so that it saves it's contents to disk so that your app state persists between launches.

I created a demo project on GitHub demonstrating how you can do this. Here is the link:

SwiftDataContainerSingleton project on GitHub Here is the README from that project:

SwiftDataContainerSingleton

A demonstration of using a data container singleton to save application state and share it between objects.

The DataContainerSingleton class is the actual singleton.

It uses a static constant sharedDataContainer to save a reference to the singleton.

To access the singleton, use the syntax

DataContainerSingleton.sharedDataContainer

The sample project defines 3 properties in the data container:

  var someString: String?
  var someOtherString: String?
  var someInt: Int?

To load the someInt property from the data container, you'd use code like this:

let theInt = DataContainerSingleton.sharedDataContainer.someInt

To save a value to someInt, you'd use the syntax:

DataContainerSingleton.sharedDataContainer.someInt = 3

The DataContainerSingleton's init method adds an observer for the UIApplicationDidEnterBackgroundNotification. That code looks like this:

goToBackgroundObserver = NSNotificationCenter.defaultCenter().addObserverForName(
  UIApplicationDidEnterBackgroundNotification,
  object: nil,
  queue: nil)
  {
    (note: NSNotification!) -> Void in
    let defaults = NSUserDefaults.standardUserDefaults()
    //-----------------------------------------------------------------------------
    //This code saves the singleton's properties to NSUserDefaults.
    //edit this code to save your custom properties
    defaults.setObject( self.someString, forKey: DefaultsKeys.someString)
    defaults.setObject( self.someOtherString, forKey: DefaultsKeys.someOtherString)
    defaults.setObject( self.someInt, forKey: DefaultsKeys.someInt)
    //-----------------------------------------------------------------------------

    //Tell NSUserDefaults to save to disk now.
    defaults.synchronize()
}

In the observer code it saves the data container's properties to NSUserDefaults. You can also use NSCoding, Core Data, or various other methods for saving state data.

The DataContainerSingleton's init method also tries to load saved values for it's properties.

That portion of the init method looks like this:

let defaults = NSUserDefaults.standardUserDefaults()
//-----------------------------------------------------------------------------
//This code reads the singleton's properties from NSUserDefaults.
//edit this code to load your custom properties
someString = defaults.objectForKey(DefaultsKeys.someString) as! String?
someOtherString = defaults.objectForKey(DefaultsKeys.someOtherString) as! String?
someInt = defaults.objectForKey(DefaultsKeys.someInt) as! Int?
//-----------------------------------------------------------------------------

The keys for loading and saving values into NSUserDefaults are stored as string constants that are part of a struct DefaultsKeys, defined like this:

struct DefaultsKeys
{
  static let someString  = "someString"
  static let someOtherString  = "someOtherString"
  static let someInt  = "someInt"
}

You reference one of these constants like this:

DefaultsKeys.someInt

Using the data container singleton:

This sample application makes trival use of the data container singleton.

There are two view controllers. The first is a custom subclass of UIViewController ViewController, and the second one is a custom subclass of UIViewController SecondVC.

Both view controllers have a text field on them, and both load a value from the data container singlelton's someInt property into the text field in their viewWillAppear method, and both save the current value from the text field back into the `someInt' of the data container.

The code to load the value into the text field is in the viewWillAppear: method:

override func viewWillAppear(animated: Bool)
{
  //Load the value "someInt" from our shared ata container singleton
  let value = DataContainerSingleton.sharedDataContainer.someInt ?? 0
  
  //Install the value into the text field.
  textField.text =  "\(value)"
}

The code to save the user-edited value back to the data container is in the view controllers' textFieldShouldEndEditing methods:

 func textFieldShouldEndEditing(textField: UITextField) -> Bool
 {
   //Save the changed value back to our data container singleton
   DataContainerSingleton.sharedDataContainer.someInt = textField.text!.toInt()
   return true
 }

You should load values into your user interface in viewWillAppear rather than viewDidLoad so that your UI updates each time the view controller is displayed.

Community
  • 1
  • 1
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 9
    I don't want to down-vote this because I think it's excellent that you invested the time to create the question and answer as a resource. Thank you. That notwithstanding, I think we do a great disservice to new developers to advocate singletons for model objects. I'm not in the "singletons are evil" camp (though noobs should google that phrase to better appreciate the issues), but I do think model data is a questionable/debatable use of singletons. – Rob Jun 19 '15 at 15:20
  • would love to see an awesome write up as yours about 2-way links – Cmag Oct 19 '16 at 05:06
  • @Duncan C Hello Duncan I am making static object in each model so I get Data from any where is it right approach or I have to follow your path cause It seems very right. – Virendra Singh Rathore Mar 14 '17 at 13:21
  • @VirendraSinghRathore, Global static variables are the worst possible way to share data across the app. They tightly couple the parts of your app together and introduce serious interdependencies. It is the exact opposite of "very right." – Duncan C Mar 14 '17 at 14:17
  • @DuncanC - would this pattern work for a CurrentUser object - basically a single user who is logged in to your app? thx – timpone Mar 14 '17 at 21:35
  • @timpone, which pattern? – Duncan C Mar 14 '17 at 22:07
  • @DuncanC - the singleton pattern in your answer. assuming yes, but just wanted to confirm. something like having a user object and then assign a notion of a CurrenUser.sharedInstance and have that be an instance of User. – timpone Mar 14 '17 at 22:24
  • I would not suggest using a User singleton. Create a general data container singleton and give that a CurrentUser property. – Duncan C Mar 14 '17 at 22:50
8

Another alternative is to use the notification center (NSNotificationCenter) and post notifications. That is a very loose coupling. The sender of a notification doesn't need to know or care who's listening. It just posts a notification and forgets about it.

Notifications are good for one-to-many message passing, since there can be an arbitrary number of observers listening for a given message.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 3
    Note that using the notification center introduces coupling that is perhaps **too** loose. It can make tracing down the flow of your program very difficult, so it should be used with care. – Duncan C Mar 14 '17 at 14:18
8

Swift 4

There are so many approaches for data passing in swift. Here I am adding some of the best approaches of it.

1) Using StoryBoard Segue

Storyboard segues are very much useful for passing data in between Source and Destination View Controllers and vice versa also.

// If you want to pass data from ViewControllerB to ViewControllerA while user tap on back button of ViewControllerB.
        @IBAction func unWindSeague (_ sender : UIStoryboardSegue) {
            if sender.source is ViewControllerB  {
                if let _ = sender.source as? ViewControllerB {
                    self.textLabel.text = "Came from B = B->A , B exited"
                }
            }
        }

// If you want to send data from ViewControllerA to ViewControllerB
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if  segue.destination is ViewControllerB {
                if let vc = segue.destination as? ViewControllerB {
                    vc.dataStr = "Comming from A View Controller"
                }
            }
        }

2) Using Delegate Methods

ViewControllerD

//Make the Delegate protocol in Child View Controller (Make the protocol in Class from You want to Send Data)
    protocol  SendDataFromDelegate {
        func sendData(data : String)
    }

    import UIKit

    class ViewControllerD: UIViewController {

        @IBOutlet weak var textLabelD: UILabel!

        var delegate : SendDataFromDelegate?  //Create Delegate Variable for Registering it to pass the data

        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            textLabelD.text = "Child View Controller"
        }

        @IBAction func btnDismissTapped (_ sender : UIButton) {
            textLabelD.text = "Data Sent Successfully to View Controller C using Delegate Approach"
            self.delegate?.sendData(data:textLabelD.text! )
            _ = self.dismiss(animated: true, completion:nil)
        }
    }

ViewControllerC

    import UIKit

    class ViewControllerC: UIViewController , SendDataFromDelegate {

        @IBOutlet weak var textLabelC: UILabel!

        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }

        @IBAction func btnPushToViewControllerDTapped( _ sender : UIButton) {
            if let vcD = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerD") as?  ViewControllerD  {
                vcD.delegate = self // Registring Delegate (When View Conteoller D gets Dismiss It can call sendData method
    //            vcD.textLabelD.text = "This is Data Passing by Referenceing View Controller D Text Label." //Data Passing Between View Controllers using Data Passing
                self.present(vcD, animated: true, completion: nil)
            }
        }

        //This Method will called when when viewcontrollerD will dismiss. (You can also say it is a implementation of Protocol Method)
        func sendData(data: String) {
            self.textLabelC.text = data
        }

    }
  • For Googlers who are as totally and utterly lost as to where to put StackOverflow answers' Swift code snippets as I am, as it seems assumed you should always know where they infer the code goes: I used Option 1) to send from `ViewControllerA` to `ViewControllerB`. I just stuck the code snippet at the bottom of my `ViewControllerA.swift` (where `ViewControllerA.swift` is actually whatever your file is named, of course) right before the last curly brace. "`prepare`" is actually a special built-in pre-existing function in a given Class [that does nothing], which is why you have to "`override`" it – velkoon Dec 04 '18 at 02:22
2

Instead of creating a data controller singelton I would suggest to create a data controller instance and pass it around. To support dependency injection I would first create a DataController protocol:

protocol DataController {
    var someInt : Int {get set} 
    var someString : String {get set}
}

Then I would create a SpecificDataController (or whatever name would currently be appropriate) class:

class SpecificDataController : DataController {
   var someInt : Int = 5
   var someString : String = "Hello data" 
}

The ViewController class should then have a field to hold the dataController. Notice that the type of dataController is the protocol DataController. This way it's easy to switch out data controller implementations:

class ViewController : UIViewController {
   var dataController : DataController?
   ...
}

In AppDelegate we can set the viewController's dataController:

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    if let viewController = self.window?.rootViewController as? ViewController {
        viewController.dataController =  SpecificDataController()
    }   
    return true
}

When we move to a different viewController we can pass the dataController on in:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    ...   
}

Now when we wish to switch out the data controller for a different task we can do this in the AppDelegate and do not have to change any other code that uses the data controller.

This is of course overkill if we simply want to pass around a single value. In this case it's best to go with nhgrif's answer.

With this approach we can separate view form the logic part.

Kristiina
  • 523
  • 2
  • 10
  • 1
    Hello, this approach is clean, testable, and what I use most of the time in small applications, but in larger ones, where not every VC (perhaps not even the root VC) may need the dependency (e.g. DataController in this case) it seems wasteful for every VC to require the dependency just to pass it around. Also, if you use different types of VC's (e.g. regular UIVC versus NavigationVC) then you need to subclass those different types just to add that dependency variable. How do you approach this? – RobertoCuba Dec 12 '18 at 16:37
1

As @nhgrif pointed out in his excellent answer, there are lots of different ways that VCs (view controllers) and other objects can communicate with each other.

The data singleton I outlined in my first answer is really more about sharing and saving global state than about communicating directly.

nhrif's answer lets you send information directly from the source to the destination VC. As I mentioned in reply, it's also possible to send messages back from the destination to the source.

In fact, you can set up an active one-way or 2-way channel between different view controllers. If the view controllers are linked via a storyboard segue, the time to set up the links is in the prepareFor Segue method.

I have a sample project on Github that uses a parent view controller to host 2 different table views as children. The child view controllers are linked using embed segues, and the parent view controller wires up 2-way links with each view controller in the prepareForSegue method.

You can find that project on github (link). I wrote it in Objective-C, however, and haven't converted it to Swift, so if you're not comfortable in Objective-C it might be a little hard to follow

Duncan C
  • 128,072
  • 22
  • 173
  • 272
1

SWIFT 3:

If you have a storyboard with identified segues use:

func prepare(for segue: UIStoryboardSegue, sender: Any?)

Although if you do everything programmatically including navigation between different UIViewControllers then use the method:

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)

Note: to use the second way you need to make your UINavigationController, you are pushing UIViewControllers on, a delegate and it needs to conform to the protocol UINavigationControllerDelegate:

   class MyNavigationController: UINavigationController, UINavigationControllerDelegate {

    override func viewDidLoad() {
        self.delegate = self
    }

    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {

     // do what ever you need before going to the next UIViewController or back
     //this method will be always called when you are pushing or popping the ViewController

    }
}
Maxim
  • 399
  • 3
  • 7
1

It depends when you want to get data.

If you want to get data whenever you want, can use a singleton pattern. The pattern class is active during the app runtime. Here is an example of the singleton pattern.

class AppSession: NSObject {

    static let shared = SessionManager()
    var username = "Duncan"
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        print(AppSession.shared.username)
    }
}

If you want to get data after any action, can use NotificationCenter.

extension Notification.Name {
    static let loggedOut = Notification.Name("loggedOut")
}

@IBAction func logoutAction(_ sender: Any) {
    NotificationCenter.default.post(name: .loggedOut, object: nil)
}

NotificationCenter.default.addObserver(forName: .loggedOut, object: nil, queue: OperationQueue.main) { (notify) in
    print("User logged out")
}
Yusuf
  • 851
  • 6
  • 14
0

The way that I would do it would be instead of passing data between view controllers, I would just declare a variable globally. You can even do this with a function!

For example:

var a = "a"
func abc() {
   print("abc")
}
class ViewController: UIViewController {

}
NDCoder
  • 93
  • 2
  • 8