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.
Control-drag from the button in the first view controller into the second view controller. In the dialog that pops up, select show. A top bar should appear in the second view controller and there should be a connection between our first view controller to our second view controller.
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.
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:
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With