Become a professional iOS developer
Get started with iOS 11 and Swift 4
Sign up for my iOS development course Zero to App Store to learn iOS development with Swift 4, and start with your professional iOS career.
When your app has multiple user interfaces, 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 advantages and drawbacks.
In this article, 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.
Quick Tip: Grab the example code for this article here on GitHub.
The easiest way to get data from view controller A to view controller B is by using a property.
A property is a variable that’s part of a class. Every instance of that class will have the 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 wrap 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 File -> New File...
, 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
.
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!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…
Want to play around with the code from this article? You can check out a complete example project on GitHub: https://github.com/reinderdevries/PassingData.
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.Quick Tip: If you want to pass a few variables that belong together, don’t create multiple properties. Instead, create a struct
or class (a model) that wraps all data, and pass along an instance of that class in one property.
That’s all there is to it! Now, let’s find out how you can do the same with segues in a Storyboards…
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 user interfaces for your apps. You can build them with Interface Builder, in Xcode, and create transitions between view controllers without 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.
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 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!
Quick Note: I’m not a fan of using Storyboards and segues in Xcode.
For many apps they 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 often 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 “guerilla”, other simply pronounce it as seg-way (like the Segway, the flopped two-wheeled self-balancing personal transporter).
Now… what if you want to pass data back from a secondary view controller to the main view controller? 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?
Learn how to code your own iOS apps by mastering Swift 4 and Xcode 9 » Find out how
Delegation is an important and pervasive software design pattern in the Cocoa Touch SDK. It’s critical to understand if you’re coding iOS apps!
With delegation, a base class can off-hand functionality to a secondary class. A coder can then implement this secondary class and respond to events from the base class. 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:
MainViewController
UIViewController
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.
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?
That’s where the NSNotificationCenter
comes in. The Notification Center handles notifications, forwarding incoming notifications to those who are listening for them. The Notification Center is the Cocoa Touch SDK’s approach to the Observer-Observable software design pattern.
Quick Note: In Swift 3+, it’s called NotificationCenter
– so no “NS” prefix. 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 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 @objc
keyword is required in 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.
So… that’s all there is to passing data between view controllers! Confusing? Clarifying?
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.
Quick Tip: Grab the example code for this article here on GitHub.
Want to learn more? Check out these resources:
Enjoyed this article? Please share it!
How To: Pass Data Between View Controllers In Swift Click To Tweet
Hi, I'm Reinder.
I help developers play with code.
in the Passing Data Back With A Closure section you have an illustrated dotted line showing data going back to the 'A' view and mention in your discussion that "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."
How this is achieved seems to be missing, which is a shame as the article is supposed to be discussing how to pass bata between view controllers.
The return within the closure doesn't do it as that just returns the value locally within 'B's onTap method.
As far as I know, and clarification would be welcome here if I am missing something, the way to pass data back would be to have declared a property on view controller A, lets call it count , and within the closure declaration add a line of code that sets count to be text.count. This works because, again omitted in the article, closures capture the context in which they are created thus giving access to the properties that were in scope at creation time.
Simon, thanks for commenting!
You're right – the closure is defined in the context of view controller A, and invoked in the context of view controller B, thus it'll return it's result to the context of view controller B. You can change properties, local variables etc. on view controller A, from the context of view controller B, by "closing over" those values when defining the closure.
A working example is provided: https://github.com/reinderdevries/PassingData In the example, "FUS-RO-DAH!" is the data that's passed back (B → A), whereas the return value ("result") is passed forwards (A → B).
One way to get direct access to a property on view controller A from view controller B is, like you're suggesting, to close over that property. View controller B can pass data back to the closure as a closure parameter. The main benefit a closure has is that you can use the local scope the closure is declared in. I wouldn't recommend using closures to pass data on occasions when delegation or a simple property will suffice. Also, when using closures, make sure you understand how capture lists work, because it's easy to create a strong reference cycle. See → https://learnappmaking.com/escaping-closures-swift-3/
I have to admit, the faint dotted line back from B to A doesn't do the closure justice – I'll think about a smarter way to show the connection.
Hi Reinder, Thanks for the quick response. I had not looked through the source code in the example when i wrote my comment but I am looking at it now. You say that '"FUS-RO_DAH" is the data thats passed back (B->A)'. i'm struggling with this if it is true because, looking at the code, nowhere in A outside of the closure which is actually running within B appears to have access to that returned value. When you said it is the data thats passed back (B->A) are you just meaning that it is made available to the closure ? If you mean its actually available to A in a wider sense , i fail to see how, and the example is not making use of it in that way. Oh and the dotted line is fine, its not the source of my confusion here :)
Ah, now I understand. In the example, the argument that's passed to the closure only exists inside the closure... except if you code the closure in such a way that the argument can escape the closure.
In a literal sense, you'd assign the passed argument to a property on view controller A. In practice, you'd do something with it, i.e. show it in a label, save it, manipulate it and pass it back to view controller B. If you only want to pass a variable back from B to A, closures are not the best approach.
Keep in mind that passing data between view controllers doesn't always mean you're literally passing around values like "42" and "Bob". You're also calling functions, setting properties, etcetera. Passing data between view controllers is much more an activity for the coder, than an activity for the app. You want to be able to read and maintain your code in a manner that's appropriate, productive and easy. If you find yourself constantly trying to figure out how to get a variable from view controller A to B, you'll want to check up on code structure and architectural design patterns.
Here's that updated diagram! The orange line is the flow of data, i.e. you provide data via the argument, the closure manipulates it or makes the data escape, and returns it back to where it was invoked.
https://uploads.disquscdn.com/images/08f165ee9d5882f5bacd931e50a5213f6987d6a289a18c21dc327b83bd8a2fea.jpg
cheers, Reinder