How To: Pass Data Between View Controllers In Swift (Extended)

Written by: Reinder de Vries, July 19 2017, in App Development

How To Pass Data Between View Controllers In Swift

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.

  1. Passing Data Forward With Properties (A → B)
  2. Passing Data Forward using Segues (A → B)
  3. Passing Data Back With Properties And Functions (A ← B)
  4. Passing Data Back With Delegation
  5. Passing Data Back With A Closure
  6. Passing Data With NSNotificationCenter
  7. Further Reading

Quick Tip: Grab the example code for this article here on GitHub.

Passing Data Forward With Properties (A → B)

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.

Passing Data Forward With Properties

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:

  • You create a constant called 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.
  • You then assign a string to the property text on vc. This is the actual passing of the data!
  • Finally, you push the new view controller onto the navigation stack with 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:

  • First, create the property for the data on view controller B (the receiving view controller). In the above example, that’s text.
  • Second, determine what happens with the data in view controller B. In the example, you assign the text to the label.
  • Third, in view controller A, create view controller B, assign a value to the property, and push it onto the navigation stack.

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…

Grab My Free iOS Development Course

Get complementary access to my course, Zero to App Store, and learn how you can build a real-time chat app with Firebase and Swift!

Yes, Send Me The Free Course!

Passing Data Forward using Segues (A → B)

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.

Passing Data Forward using Segues

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:

  • First, with the 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.
  • Then, you simply cast 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.
  • Finally, you set the 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).

Passing Data Back With Properties And Functions (A ← B)

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:

  • The user of your app has gone from view controller A to a secondary view controller B.
  • In the secondary view controller the user interacts with a piece of data, and you want that data back in view controller A.

In other words: instead of passing data from A → B, you want to pass data back from B → A.

Passing Data Back With A Property And Function

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:

  • The 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.
  • The above code example creates a retain cycle. The secondary view controller can’t be removed from memory until the main view controller is removed, but the main view controller can’t be removed from memory until the secondary view controller is removed. (A solution would be the weak property keyword.)
  • Two developers can’t work separately on 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?

Quick Note: Are you learning how to code iOS apps? Check out Zero to App Store, the extremely practical iOS development course I created.
» Learn more about Zero to App Store

Passing Data Back With Delegation

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!

Passing Data Back With Delegation

Here’s a quick example:

  • Imagine you’re working in a pizza restaurant. You have a pizza baker that makes pizza.
  • Customers can do anything they want with the pizza, like eat it, put it in the freezer, or share it with a friend.
  • The pizza baker used to care about what you do with your pizza, but now he’s decoupled himself from the pizza-eating process and just throws you a pizza when it’s ready. He delegates the pizza handling process and only concerns himself with baking pizzas!

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:

  • Name the class MainViewController
  • Extend (or subclass) the UIViewController class
  • Implement (or conform to) the PizzaDelegate class

If 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:

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:

  • You need a delegate protocol
  • You need a delegate property
  • The class that you want to off-hand data to, needs to conform to the protocol
  • The class that you want to delegate from, needs to call the function defined in the protocol

How is this different from the previous example with passing data back via properties?

  • The developers that work on separate classes only need to agree on the protocol and the functions it has. Either developer can choose to conform and implement whatever they want.
  • There’s no direct connection between the main view controller and the secondary view controller, which means that they’re more loosely coupled than the previous example.
  • The protocol can be implemented by any class, not just the MainViewController.

Awesome! Now let’s look at another example… using closures.

Grab My Free iOS Development Course

Get complementary access to my course, Zero to App Store, and learn how you can build a real-time chat app with Firebase and Swift!

Yes, Send Me The Free Course!

Passing Data Back With A Closure

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.

Passing Data Back With A Closure

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:

  • The closure completionHandler is called, with one string argument. Its result is assigned to result.
  • The result is printed out with 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:

  • You don’t need a complete delegation approach, with a protocol, you just want to create a quick function.
  • You want to pass a closure through multiple classes. Without a closure you’d have to create a cascading set of function calls, but with the closure you can just pass the block of code along.
  • You need to locally define a block of code with a closure, because the data you want to work with only exists locally.

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?

Passing Data With NSNotificationCenter

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.

Passing Data With NSNotificationCenter

Working with Notification Center has three key components:

  • Observing the notification
  • Sending the notification
  • Responding to the notification

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:

  • You use 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.
  • You then call the function addObserver(_:selector:name:object:) on the Notification Center.
    • The first argument is the instance that does the observation, and it’s almost always self.
    • The second argument is the selector you want to call when the notification is observed, and this is almost always a function of the current class.
    • The third parameter is the name of the notification, so you pass the static constant notificationName.
    • The fourth parameter is the object whose notifications you want to receive. You usually pass 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:

  • You call the function post(name:object:userInfo:) on the default Notification Center, exactly the same center as you used before.
  • The first function argument is the name of the notification, that static constant that you defined before.
  • The second argument is the object posting the notification. You can often leave this 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.
  • The third argument is the notification payload called 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:

  • The view controllers or other classes you want to pass data between are not closely related. Think about a table view controller that needs to respond when a REST API receives new data.
  • The view controllers don’t necessarily have to exist yet. It could happen that the REST API receives data before the table view is put on screen. Observing for notifications is optional, which is an advantage if parts of your app are ephemeral.
  • Many view controllers need to respond to one notification, or one view controller needs to respond to multiple notifications. Notification Center is many-to-many.

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.

Grab My Free iOS Development Course

Get complementary access to my course, Zero to App Store, and learn how you can build a real-time chat app with Firebase and Swift!

Yes, Send Me The Free Course!

Further Reading

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

Written By: Reinder de Vries

Reinder de Vries is an indie app developer who teaches aspiring app developers and marketers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed over 50 apps for iOS, Android and the web, and his code is used by millions of users all over the globe. When Reinder isn't building apps, he enjoys strong espresso and traveling.

Grab My Free iOS Development Course

Get complementary access to my course, Zero to App Store, and learn how you can build a real-time chat app with Firebase and Swift!

Yes, Send Me The Free Course!

Comments & Thoughts


  • Simon Charles Gardener

    cheers, Reinder

  • 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

  • Simon Charles Gardener

    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 :)

  • 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.

  • Simon Charles Gardener

    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.