How do you share data between view controllers and other objects in Swift?

Let's say I have several view controllers in my Swift application, and I want to be able to transfer data between them. If I have several levels in the view manager stack, how do I transfer data to another view controller? Or between tabs in a tab bar view controller?

(Please note that this question is a “jingle”.) He is asked so much that I decided to write a textbook on this subject. See my answer below.

+70
ios swift
Apr 19 '15 at 19:43
source share
7 answers

Your question is very broad. To suggest that there is one simple solution for each scenario is a bit naive. So let's look at some of these scenarios.




The most common scenario that I talked about about stack overflows in my experience is a simple transfer of information from one view controller to the next.

If we use a storyboard, our first view controller can override prepareForSegue , which is what it is for. The UIStoryboardSegue object UIStoryboardSegue passed when this method is called and contains a link to our destination view controller. Here we can set the values ​​that we want to convey.

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

Alternatively, if we do not use storyboards, we load our view controller from the tip. Our code is a bit simpler.

 func showNextController() { let destination = SecondController(nibName: "SecondController", bundle: NSBundle.mainBundle()) destination.myInformation = self.myInformation self.showViewController(destination, sender: self) } 

In both cases, myInformation is a property on each view controller in which all data must be transferred from one view controller to another. Obviously, they do not have to have the same name on each controller.




We can also exchange information between tabs in the UITabBarController .

In this case, it is actually potentially even simpler.

First, subclass UITabBarController and give it properties for any information that we want to share between the various tabs:

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

Now, if we create our application from the storyboard, we simply change the controller class of the tab bar from the default from UITabBarController to MyCustomTabController . If we do not use the storyboard, we simply create an instance of this custom class, not the default class UITabBarController and add our view controller to it.

Now all of our view controllers in 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 the UINavigationController in the same way, we can use the same approach for sharing data throughout the navigation stack:

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



There are several other scenarios. In no case does this answer cover all of them.

+80
Apr 20 '15 at 1:15
source share

This question arises constantly.

One suggestion is to create a singleton data container: an object that is created once and only once in the life of your application and stored for the duration of your application.

This approach is well suited for situations where you have global application data that must be accessible / modifiable for different classes in your application.

Other approaches, such as setting up one-way or two-way links between view controllers, are better suited for situations where you transfer information / messages directly between view controllers.

(see nhgrif answer below for other alternatives.)

With a singleton data container, you add a property to your class that holds a reference to your singleton, and then use this property anytime you need access.

You can configure your singleton so that it saves its contents to disk so that your application state is maintained between starts.

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

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

SwiftDataContainerSingleton

Demonstration of using data container syntax to preserve application state and share it between objects.

The DataContainerSingleton class is a valid singleton.

It uses the sharedDataContainer static constant to maintain a link to a singleton.

To access the singleton, use the syntax

 DataContainerSingleton.sharedDataContainer 

An example project defines 3 properties in a data container:

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

To load the someInt property from a data container, you must use the following code:

 let theInt = DataContainerSingleton.sharedDataContainer.someInt 

To save the value of someInt, you must use the syntax:

 DataContainerSingleton.sharedDataContainer.someInt = 3 

The DataContainerSingleton init method adds an observer for UIApplicationDidEnterBackgroundNotification . This code is as follows:

 goToBackgroundObserver = NSNotificationCenter.defaultCenter().addObserverForName( UIApplicationDidEnterBackgroundNotification, object: nil, queue: nil) { (note: NSNotification!) -> Void in let defaults = NSUserDefaults.standardUserDefaults() //----------------------------------------------------------------------------- //This code saves the singleton 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 observer code, it stores the properties of the NSUserDefaults data NSUserDefaults . You can also use NSCoding , Core Data, or various other ways to store state data.

The DataContainerSingleton init method also attempts to load stored values ​​for its properties.

This part of the init method looks like this:

 let defaults = NSUserDefaults.standardUserDefaults() //----------------------------------------------------------------------------- //This code reads the singleton 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 ​​in NSUserDefault are stored as string constants, which are part of the DefaultsKeys structure, defined as follows:

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

You are referring to one of the following constants:

 DefaultsKeys.someInt 

Using singleton data container:

This sample application uses the three way syntax of the data container.

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

Both view managers have a text field on them, and both load the value from the singlelton someInt someInt data container into the text field in their viewWillAppear method and both store the current value from the text field back to the `someInt 'data container.

The code for loading 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 for saving the user-modified value back to the data container is in the dispatcher methods of the textFieldShouldEndEditing type:

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

You must load the values ​​into your user interface in viewWillAppear and not in viewDidLoad so that your user interface is updated every time the view controller is displayed.

+40
Apr 19 '15 at 19:46
source share

Another alternative is to use the notification center (NSNotificationCenter) and send notifications. This is a very loose connection. The sender of the notification does not need to know or care about who is listening. He simply publishes a notice and forgets about it.

Notifications are good for sending one-to-many messages, as there can be an arbitrary number of observers listening to this message.

+7
Apr 20 '15 at 2:37
source share

Swift 4

There are so many approaches for transferring data in swift. Here I add some of the best approaches.

1) Using StoryBoard Segue

Storyboard segments are very useful for transferring data between Source and Destination View Controllers and vice versa.

 // 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 } } 
+4
Jan 10 '18 at 6:11
source share

SWIFT 3:

If you have a storyboard with specific segments, use:

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

Although if you are all programmatically navigating between different UIViewControllers, use the method:

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

Note. To use the second way that you need your UINavigationController, you click the UIViewControllers on, the delegate, and it must conform to the UINavigationControllerDelegate protocol:

  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 } } 
+2
30 Oct '16 at 17:23
source share

Instead of creating a controller data controller, I would suggest creating an instance of the data controller and passing it on. To support dependency injection, I would first create the DataController protocol:

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

Then I would create a SpecificDataController (or any other name would be appropriate for now) class:

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

The ViewController class must have a field for storing the DataController . Note that the DataController type is the DataController protocol. Thus, it is easy to disable the implementation of the data controller:

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

In AppDelegate we can set the viewController 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 another viewController, we can pass the DataController on to:

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

Now that we want to disable the data controller for another task, we can do this in AppDelegate and we don’t need to change any other code that uses the data controller.

This, of course, is unnecessary if we just want to convey a single value. In this case, it is best to go with an nhgrif answer.

With this approach, we can separate the form of the logical part.

+2
Mar 16 '17 at 12:36 on
source share

As @nhgrif noted in his excellent answer, there are many ways that VCs (view controllers) and other objects can communicate with each other.

The data syntax that I outlined in my first answer is really more about sharing and maintaining a global state than direct communication.

The nhrif response allows you to send information directly from the source to the target VC. As I mentioned in the answer, it can also send messages back from the destination to the source.

In fact, you can configure an active one-way or two-way channel between different controllers. If view controllers are linked using a storyboard, the time required to configure the links is in the prepareFor Segue method.

I have an example Github project that uses a parent view controller to host two different table views as children. The child view controllers are connected using inline segments, and the parent view controller connects two-way links with each view controller in the prepareForSegue method.

You can find this project on github (link). However, I wrote it in Objective-C and did not convert it to Swift, so if you are not comfortable with Objective-C, it can be a little difficult to follow

+1
Apr 20 '15 at 2:34
source share



All Articles