
If your app has multiple User Interfaces (UIs), you’ll want to move data from one UI to the next. How do you pass data between view controllers in Swift?
Passing data between view controllers is an important part of iOS development. You can use several ways to do so, and all of them have distinct advantages and drawbacks.
The ability to pass data between view controllers with ease is affected by your choice of app architecture. App architecture affects how you work with view controllers, and vice versa.
In this tutorial, you’ll learn 6 different methods of passing data between view controllers, including working with properties, segues and NSNotificationCenter. You’ll start with the easiest approach, then move on to more complicated practices.
Ready? Let’s go.
Looking to pass data between SwiftUI views? You can find the SwiftUI counterpart to this UIKit-based tutorial here: How To: Pass Data Between Views with SwiftUI
You can pass data between view controllers in Swift in 6 ways:
As you’ll soon learn, some of these approaches are one-way, so you can send some data one way, but not the other way around. They’re not bilateral, so to speak. But don’t worry – in most cases you only need one-way communication anyway!
The easiest way to get data from view controller A to view controller B (forward) is by using a property.
A property is a variable that’s part of a class. Every instance of that class will have that property, and you can assign a value to it. View controllers of type UIViewController can have properties just like any other class.
Here’s a view controller MainViewController with a property called text:
class MainViewController: UIViewController
{
var text:String = ""
override func viewDidLoad()
{
super.viewDidLoad()
}
}
Whenever you create an instance of MainViewController, you can assign a value to the text property. Like this:
let vc = MainViewController()
vc.text = "Hammock lomo literally microdosing street art pour-over"
Easy, right?
Let’s say this view controller is part of a navigation controller and we want to pass some data to a second view controller.
First, if you’re using Storyboards you’ll want to embed this MainViewController in a navigation controller. If you’re not using Storyboards, you can create a new navigation controller and set the MainViewController as its root view controller. If you’re already using navigation controllers, perfect!
Then, you create a new view controller subclass and a view controller .xib file. You can do this by choosing , then Cocoa Touch Class, then create the SecondaryViewController class. Don’t forget to tick the Also create XIB file checkbox.
This is the code for the view controller:
class SecondaryViewController: UIViewController
{
var text:String = ""
@IBOutlet weak var textLabel:UILabel?
override func viewDidLoad()
{
super.viewDidLoad()
textLabel?.text = text
}
}
In the XIB file, add a UILabel to the view and connect it to the textLabel outlet. You now have a view controller with a text property, and a label element that’s connected to the property.
As you can see in the sample code above, in the method viewDidLoad() the text property of textLabel is assigned the value from the text property of SecondaryViewController.
Quick Tip: Are you not sure about your iOS development workflow? Check out this tutorial for best practices.
Then, here’s the actual passing of the data… Add the following method to MainViewController:
@IBAction func onButtonTap()
{
let vc = SecondaryViewController(nibName: "SecondaryViewController", bundle: nil)
vc.text = "Next level blog photo booth, tousled authentic tote bag kogi"
navigationController?.pushViewController(vc, animated: true)
}
If you then add a button to MainViewController, and connect it to the action above, the code should execute when you tap the button!
Here’s what happens in that piece of code:
vc and assign it an instance of SecondaryViewController. You pass the right XIB name in the initializer to make sure the view controller uses the correct XIB file.text on vc. This is the actual passing of the data between the view controllers!pushViewController(_:animated:). The change is animated, so when it’s executed you’ll see the new view controller “slide in” from the right of the iPhone screen.Got it? It’s a confusing amount of code for such a small thing…
Let’s break it down. Here’s how you pass data forward from view controller A to view controller B, with a property:
text.text to the label.That’s all there is to it! Now, let’s find out how you can do the same with segues in a Storyboards…
Quick Tip: SwiftUI changes the playing field for view controllers, because… SwiftUI doesn’t have view controllers! Instead, we got views, property wrappers, bindings, Combine, and much more. Learn more about SwiftUI here: Get Started with SwiftUI for iOS
If you’re using Storyboards, you can pass data between view controllers with segues, using the prepare(for:sender:) function.
Passing data between view controllers, using Storyboards, isn’t that much different from using XIBs. Let’s find out how to pass data forward using segues.
Here’s a quick refresher on Storyboards and segues. A Storyboard is essentially an assortment of UIs for your apps. You can build them with Interface Builder, in Xcode, and create transitions between view controllers with minimal code.
A segue is simply a fancy word for “smooth transition”. When you switch from one view controller to the next, with a navigation controller for instance, you make a segue. In your view controller, you can hook into this segue and customize it. Passing data between view controllers happens during a segue.
In the example project, you can see the segue from MainViewController to TertiaryViewController. This is as simple as adding an action from the button to the 3rd view controller, and choosing the Show type. An arrow from MainViewController to TertiaryViewController now appears. You then set the Custom Class on the view controller, in the Identity Inspector, to TertiaryViewController to control the view controller with code.
Want to play around with the code from this tutorial? You can check out a complete example project on GitHub: https://github.com/reinderdevries/PassingData.
Here’s the code from the TertiaryViewController class:
class TertiaryViewController: UIViewController
{
var username:String = ""
@IBOutlet weak var usernameLabel:UILabel?
override func viewDidLoad()
{
super.viewDidLoad()
usernameLabel?.text = username
}
}
It’s nothing special – much like the previous example, you’re setting a simple label usernameLabel with a text from property username.
Then, to pass the data from MainViewController to TertiaryViewController you use a special function called prepare(for:sender:). This method is invoked before the segue, so you can customize it.
Here’s the segue code in action:
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.destination is TertiaryViewController {
let vc = segue.destination as? TertiaryViewController
vc?.username = "Arthur Dent"
}
}
This is what happens:
if statement and the is keyword you check whether the segue destination is of class TertiaryViewController. You need to identify if this is the segue you want to customize, because all segues go through the prepare(for:sender:) function.segue.destination to TertiaryViewController, so you can use the username property. The destination property on segue has type UIViewController, so you’ll need to cast it to get to the username property.username property, just like you did in the previous example.The funny thing about the prepare(for:sender:) function is that you don’t have to do anything else. The function simply hooks into the segue, but you don’t have to tell it to continue with the transition. You also don’t have to return the view controller you customized.
You can also improve the above code sample like this:
if let vc = segue.destination as? TertiaryViewController {
vc.username = "Ford Prefect"
}
Instead of using the is keyword to check the type of destination, and then casting it, you now do that in one go with optional casting. When segue.destination isn’t of type TertiaryViewController, the as? expression returns nil and therefore the conditional doesn’t execute. Easy-peasy!
If you don’t want to use type casting, you can also use the segue.identifier property. Set it to tertiaryVC in the Storyboard, and then use this:
if segue.identifier == "tertiaryVC" {
// Do stuff...
}
So… that’s all there is to passing data between view controllers using segues and Storyboards!
For many apps Storyboards limit the different transitions between view controllers you can use. Storyboards often overcomplicate building user interfaces in Xcode, at very little benefit. On top of that, Interface Builder gets slow and laggy if you have complicated Storyboards or XIBs.
Everything you can do with Storyboards you can code by hand, with much greater control and little extra developer effort. I’m not saying you should code your user interfaces by hand, though! Use one XIB per view controller, much like the example above, and subclass views like UITableViewCell.
Ultimately, as a coder, you’ll want to figure out on your own what you like best – tabs or spaces, Storyboards or XIBs, Core Data or Realm – it’s up to you!
Fun Fact: Every developer has their own way of saying the word “segue”. Some pronounce se- as “say” or “set”, and -gue as “gue_rilla”, other simply pronounce it as _seg-way (like the Segway, the flopped two-wheeled self-balancing personal transporter for tourists).
Now… what if you want to pass data back from a secondary view controller to the main view controller?
Passing data between view controllers using a property on the secondary view controller, as explained in the first chapter, is fairly straightforward. How do you pass data back from the second view controller to the first? You can do this in a couple of ways, as you’ll find out in the next sections.
Here’s the scenario:
In other words: instead of passing data from A → B, you want to pass data back from B → A.
The easiest way to pass data back is to create a reference to view controller A on view controller B, and then call a function from view controller A within view controller B.
This is now the secondary view controller class:
class SecondaryViewController: UIViewController
{
var mainViewController:MainViewController?
@IBAction func onButtonTap()
{
mainViewController?.onUserAction(data: "The quick brown fox jumps over the lazy dog")
}
}
Then, this function is added to MainViewController:
func onUserAction(data: String)
{
print("Data received: \(data)")
}
When the view controller is pushed onto the navigation stack, just like in the previous examples, a connection between the main view controller and the secondary view controller is made:
let vc = SecondaryViewController(nibName: "SecondaryViewController", bundle: nil)
vc.mainViewController = self
In the example above, self is assigned to property mainViewController. The secondary view controller now “knows” the main view controller, so it can call any of its functions – like onUserAction(data:).
That’s all there is to it. But… this approach for passing data isn’t the most ideal. It has a few major drawbacks:
MainViewController and SecondaryViewController are now tightly coupled. You want to avoid tight-coupling in software design, mostly because it decreases the modularity of your code. Both classes become too entangled, and rely on each other to function properly, with often leads to spaghetti code.weak property keyword.)MainViewController and SecondaryViewController, because both view controllers need to have an understanding about how the other view controller works. There’s no separation of concerns.You want to avoid directly referencing classes, instances and functions like this. Code like this simply becomes a nightmare to maintain. It often leads to spaghetti code, in which you change one piece of code that breaks another seemingly unrelated piece of code…
So, what’s a better idea? Delegation!
Quick Tip: If you want to pass a few variables that belong together between view controllers, don’t create multiple properties. Instead, create a struct or class (a so-called model) that wraps all data, and pass along an instance of that class in one property.
Delegation is an important and frequently used software design pattern in the iOS SDK. It’s critical to understand if you’re coding iOS apps!
With delegation, a base class can hand off functionality to a secondary class. A coder can then implement this secondary class and respond to events from the base class, by making use of a protocol. It’s decoupled!
Here’s a quick example:
Before you and the pizza baker can understand each other, you need to define a protocol:
protocol PizzaDelegate {
func onPizzaReady(type: String)
}
A protocol is an agreement about what functions a class should implement, if it wants to conform to the protocol. You can add it to a class like this:
class MainViewController: UIViewController, PizzaDelegate
{
···
This class definition now says:
MainViewControllerUIViewController classPizzaDelegate classIf you say you want to conform to a protocol, you also have to implement it. You add this function to MainViewController:
func onPizzaReady(type: String)
{
print("Pizza ready. The best pizza of all pizzas is... \(type)")
}
When you create the secondary view controller, you also create the delegate connection, much like the property in the previous example:
vc.delegate = self
Then, here’s the key aspect of delegation. You now add a property and some code to the class that should delegate functionality, like the secondary view controller.
First, the property:
weak var delegate:PizzaDelegate?
Then, the code:
@IBAction func onButtonTap()
{
delegate?.onPizzaReady(type: "Pizza di Mama")
}
Let’s say that the function onButtonTap() is called when the pizza baker finishes making a pizza. It then calls onPizzaReady(type:) on delegate.
The pizza baker doesn’t care if there’s a delegate or not. If there’s no delegate, the pizza just ends up thrown away. If there’s a delegate, the pizza baker only hands-off the pizza – you can do with it what you want!
So, let’s take a look at the key components from delegation:
How is this different from the previous example with passing data back via properties?
MainViewController.Awesome! Now let’s look at another example… using closures.
Why is that delegate property marked with weak? Find out more here: Automatic Reference Counting (ARC) in Swift
Using a closure to pass data between view controllers isn’t much different from using a property or delegation.
The biggest benefit of using a closure is that it’s relatively easy to use, and you can define it locally – no need for a function or protocol.
You start with creating a property on the secondary view controller, like this:
var completionHandler: ((String) -> Int)?
It’s a property completionHandler that has a closure type. The closure is optional, denoted by the ?, and the closure signature is (String) -> Int. This means the closure has one parameter of type String and returns one value of type Int.
Once more, in the secondary view controller, we call the closure when a button is tapped:
@IBAction func onButtonTap()
{
let result = completionHandler?("FUS-ROH-DAH!!!")
print("completionHandler returns... \(result)")
}
In the example above, this happens:
completionHandler is called, with one string argument. Its result is assigned to result.print()Then, in the MainViewController you can define the closure like this:
vc.completionHandler = { text in
print("text = \(text)")
return text.characters.count
}
This is the closure itself. It’s declared locally, so you can use all local variables, properties and functions.
In the closure the text parameter is printed out, and then the string length is returned as the result of the function.
This is where it gets interesting. The closure lets you pass data between view controllers bi-directionally! You can define the closure, work with the data that’s coming in, and return data to the code that invokes the closure.
You may note here that a function call, with delegation or a direct property, also allows you to return a value to the caller of the function. That’s absolutely true!
Closures might come in handy in the following scenarios:
One of the risks of using closures to pass data between view controllers is that your code can become very dense. It’s smartest to only use closures to pass data between view controllers if it makes sense to use closures over any other method – instead of just using closures because they’re so convenient!
So… what if you want to pass data between view controllers that don’t have, or can’t have, a connection between them?
You can pass data between view controllers with Notification Center, via its NotificationCenter class.
The Notification Center handles notifications, and forwards incoming notifications to components that are listening for them. The Notification Center is the iOS SDK’s approach to the Observer-Observable software design pattern.
Quick Note: Since Swift 3, it’s called NotificationCenter – so no “NS” prefix. And keep in mind that these “notifications” aren’t push notifications.
Working with Notification Center has three key components:
Let’s first start with observing the notification. Before you can respond to a notification, you need to tell the Notification Center that you want to observe it. The Notification Center then tells you about any notifications it comes across, because you’ve indicated you’re on the lookout for them.
Every notification has a name to identify them. In MainViewController you add the following static property to the top of the class:
static let notificationName = Notification.Name("myNotificationName")
This static property, also known as a class property, is available anywhere in the code by calling MainViewController.notificationName. This is how you identify the notification with one single constant. You wouldn’t want to mix up your notifications by mistyping it somewhere!
Here’s how you observe for that notification:
NotificationCenter.default.addObserver(self, selector: #selector(onNotification(notification:)), name: MainViewController.notificationName, object: nil)
You usually add this in viewDidLoad() or viewWillAppear(_:), so that the observation is registered when the view controller is put on screen. Here’s what happens in the code sample above:
NotificationCenter.default, which is the default Notification Center. You could create your own Notification Center, for instance for a certain kind of notifications, but chances are the default center is fine.addObserver(_:selector:name:object:) on the Notification Center.
self.notificationName.nil here, but you could use it to only observe notifications from one particular object.At a later point you can stop observing the notification with this:
NotificationCenter.default.removeObserver(self, name: MainViewController.notificationName, object: nil)
You can also stop observing for all registered notifications with:
NotificationCenter.default.removeObserver(self)
Remember that notifications are explicit, so you always observe one type of notification that results in one function call on one object (usually self) when the notification occurs.
The function that will get called when the notification occurs is onNotification(notification:), so let’s add that to the class:
@objc func onNotification(notification:Notification)
{
print(notification.userInfo)
}
The keyword is required since Swift 4, because the NSNotificationCenter framework is Objective-C code. In the function, you’ll simply print out the notification payload with notification.userInfo.
Then, posting the notification is easy. Here’s how you do that:
NotificationCenter.default.post(name: MainViewController.notificationName, object: nil, userInfo: ["data": 42, "isImportant": true])
Again, there’s a few moving parts:
post(name:object:userInfo:) on the default Notification Center, exactly the same center as you used before.nil, but if you’ve used the object argument when observing the notification you can pass the same object here to exclusively observe and post for that object.userInfo. You can pass a dictionary with any kind of data here. In this example, you’re passing some data and a boolean value.That’s all there is to it!
The Notification Center comes in handy in a few scenarios:
You can think of the Notification Center as a superhighway for information, where notifications are constantly sent over its lanes, in many directions and configurations.
If you just want some “local traffic” between view controllers, it doesn’t make sense to use Notification Center – you’d use a simple delegate, property or closure instead. But if you want to repeatedly and regularly send data from one part of your app to the other, Notification Center is a great solution.
You can learn more about working with NotificationCenter here: How To: Using Notification Center In Swift.
So… that’s all there is to passing data between view controllers! Confusing? Clarifying? Either way, you’re now ready to apply what you’ve learned into your own iOS projects.
If you’re coding iOS apps, you’ll want to practice passing data between view controllers. Always keep in mind that a good software architecture solves many future problems and bugs.
Want to learn more? Check out these resources: