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.
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:Then I would create a
SpecificDataController
(or whatever name would currently be appropriate) class:The
ViewController
class should then have a field to hold thedataController
. Notice that the type ofdataController
is the protocolDataController
. This way it's easy to switch out data controller implementations:In
AppDelegate
we can set the viewController'sdataController
:When we move to a different viewController we can pass the
dataController
on in: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.
SWIFT 3:
If you have a storyboard with identified segues use:
Although if you do everything programmatically including navigation between different UIViewControllers then use the method:
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:
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
The sample project defines 3 properties in the data container:
To load the
someInt
property from the data container, you'd use code like this:To save a value to someInt, you'd use the syntax:
The DataContainerSingleton's
init
method adds an observer for theUIApplicationDidEnterBackgroundNotification
. That code looks like this:In the observer code it saves the data container's properties to
NSUserDefaults
. You can also useNSCoding
, 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:
The keys for loading and saving values into NSUserDefaults are stored as string constants that are part of a struct
DefaultsKeys
, defined like this:You reference one of these constants like this:
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 UIViewControllerSecondVC
.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 theirviewWillAppear
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:The code to save the user-edited value back to the data container is in the view controllers'
textFieldShouldEndEditing
methods: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.
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
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.
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. AUIStoryboardSegue
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.Alternatively, if we're not using storyboards, then we're loading our view controller from a nib. Our code is slightly simpler then.
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:Now, if we're building our app from the storyboard, we simply change our tab bar controller's class from the default
UITabBarController
toMyCustomTabController
. If we're not using a storyboard, we simply instantiate an instance of this custom class rather than the defaultUITabBarController
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:
And by subclassing
UINavigationController
in the same way, we can take the same approach to share data across an entire navigation stack:There are several other scenarios. By no means does this answer cover all of them.