Data Exchange in Swift ViewControllers: How to passData to ViewControllers

Unlock the secrets of data communication in Swift through practical techniques and examples

Vikram Kumar
7 min readOct 31, 2023

Introduction:

In iOS app development, transferring data from one ViewController to another is a routine and essential task. It involves sending various kinds of information, such as user preferences, configuration settings, or user-generated content, between different parts of your app. Swift offers several techniques for achieving this data transfer seamlessly. In this article, we’ll explore different methods for passing data to ViewControllers in Swift.

Data Exchange in Swift ViewControllers

1. Using Segues and Prepare for Segue

Segues are the primary means of transitioning between ViewControllers in iOS applications. The “Prepare for Segue” method is a valuable tool for configuring the destination ViewController before the transition. Here’s how you can effectively use this method to pass data:

  1. Create a Segue: Start by creating a segue in your storyboard, connecting the source ViewController to the destination ViewController.
  2. Assign an Identifier: In the Attributes Inspector, give the segue an identifier (e.g., “MySegue”).
  3. Implement prepare(for:sender:): In your source ViewController, override the prepare(for segue: UIStoryboardSegue, sender: Any?) method.
  4. Set the Data: Inside the prepare(for:sender:) method, you can access the destination ViewController and assign the data to its properties
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue" {
if let destinationVC = segue.destination as? DestinationViewController {
destinationVC.dataToPass = "Hello, World!"
}
}
}

2. Using Closures

Closures are powerful for passing data backward from a destination ViewController to the source ViewController. This method is particularly handy when you need to return data from a modal ViewController.

In this section, we’ll address a common scenario in app development: editing and updating data, specifically notes. When a user edits a note, we want to pass the updated note data back to the list of notes.

How Closures Facilitate Data Transfer

Closures provide an elegant way to pass data between ViewControllers. Here’s how it works in this real-world example:

  • The Closure Property: In the “Edit Note” ViewController, we declare a closure property that can carry the updated note data.
  • Calling the Closure: When the user finishes editing the note, the closure is called, carrying the updated note data.
  • Handling the Data: In the “List of Notes” ViewController, the closure is executed, and the updated note is processed and displayed.

Coding Example

Let’s create two ViewControllers: “Edit Note” and “List of Notes.”

Edit Note ViewController:

import UIKit

class EditNoteViewController: UIViewController {
// Declare a closure property for passing data
var updateNoteClosure: ((Note) -> Void)?

// Note object to be updated
var noteToEdit: Note?

// Function to save the edited note
@IBAction func saveNoteButtonTapped(_ sender: UIButton) {
// Perform note editing logic here

// Call the closure with the updated note
if let updatedNote = noteToEdit {
updateNoteClosure?(updatedNote)
}

// Dismiss the ViewController
dismiss(animated: true, completion: nil)
}
}

List of Notes ViewController:

import UIKit

class ListOfNotesViewController: UIViewController {
// ...

// Function to handle the updated note using the closure
func handleUpdatedNote(updatedNote: Note) {
// Update the note in the list of notes
// Perform UI updates as needed
}
}

// ...

extension ListOfNotesViewController: UITableViewDelegate {

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)

// Check if we can instantiate the "EditNoteViewController" from the storyboard
if let editNoteVC = storyboard?.instantiateViewController(withIdentifier: "EditNoteViewController") as? EditNoteViewController {

// Pass the selected note to the "EditNoteViewController" for editing
editNoteVC.noteToEdit = notes[indexPath.row]

// Define a closure to handle the updated note when the edit is completed
editNoteVC.updateNoteClosure = { [weak self] editedNote in
// Use 'self' weakly to prevent retain cycles
self?.handleUpdatedNote(updatedNote: editedNote)
}
navigationController?.pushViewController(editNoteVC, animated: true)
}
}
}

In this example, when a user finishes editing a note in the “Edit Note” ViewController and taps the “Save” button, the updateNoteClosure is called, carrying the updated Note object. The "List of Notes" ViewController captures this closure and uses it to handle and update the note in its list of notes.

This real-world example demonstrates how closures can facilitate seamless data transfer and enhance the user experience when editing and updating data within your app. It offers a flexible and efficient approach to handle data communication between different parts of your application.

3. Using Delegation

In Swift, a delegate is a design pattern used to establish a one-to-one communication relationship between objects. A delegate allows one object to send messages or events to another object when specific actions or events occur. Delegation is commonly used for implementing callbacks, responding to user interactions, and customizing the behavior of objects.

Delegation is a well-established design pattern in iOS. It allows you to send data back to a source ViewController.

How Delegation Works

Delegation in Swift involves a few key steps:

  1. Protocol Declaration: Define a protocol that outlines the methods the delegate (destination ViewController) should implement to receive data.
  2. Delegate Property: Create a delegate property in the source ViewController, allowing the delegate (destination ViewController) to be set.
  3. Data Passing: When the source ViewController needs to send data, it calls the delegate’s method, passing the data as a parameter.
  4. Implementation: In the delegate (destination ViewController), conform to the protocol and implement the method to receive and process the data.

Coding Example

Let’s consider a real-world example where we use delegation to pass a note object from an “Edit Note” ViewController back to a “List of Notes” ViewController.

Source ViewController:

import UIKit

// Define a protocol for data delegation
protocol NoteDelegate: class {
func didUpdateNote(updatedNote: Note)
}

class EditNoteViewController: UIViewController {
// Create a delegate property
weak var delegate: NoteDelegate?

// ...

// Function to save the edited note
@IBAction func saveNoteButtonTapped(_ sender: UIButton) {
// Perform note editing logic here

// Call the delegate method to send the updated note
if let updatedNote = editedNote {
delegate?.didUpdateNote(updatedNote: updatedNote)
}

// Dismiss the ViewController
dismiss(animated: true, completion: nil)
}
}

Destination ViewController:

import UIKit

class ListOfNotesViewController: UIViewController, NoteDelegate {
// Create a property to store the list of notes (assuming 'notes' is an array of Note objects)
var notes: [Note] = []

// Implement the delegate method to handle updated notes
func didUpdateNote(updatedNote: Note) {
// Find the index of the updated note in the 'notes' array
if let index = notes.firstIndex(where: { $0.id == updatedNote.id }) {
// Update the note with the edited content
notes[index] = updatedNote

// Reload the table view or update the UI to reflect the changes
tableView.reloadData()
}
}

// Additional UI elements and methods for displaying and managing notes
// ...
}

// ...

extension ListOfNotesViewController: UITableViewDelegate {

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)

// Check if we can instantiate the "EditNoteViewController" from the storyboard
if let editNoteVC = storyboard?.instantiateViewController(withIdentifier: "EditNoteViewController") as? EditNoteViewController {

// Set the delegate of the 'EditNoteViewController'
editNoteVC.delegate = self

// Define a closure to handle the updated note when the edit is completed
editNoteVC.delegate = self
navigationController?.pushViewController(editNoteVC, animated: true)
}
}
}

In this example, the “Edit Note” ViewController declares a NoteDelegate protocol and sets up a delegate property. When a user finishes editing a note and taps the "Save" button, the delegate method is called, sending the updated note. The "List of Notes" ViewController conforms to the protocol and handles the updated note by implementing the delegate method.

4. Setting Data Directly to ViewController Instance

You can also set data directly to a ViewController instance when instantiating it through a UIStoryboard. Here’s how to do it:

Source ViewController:
In this example, we’ll assume you have a “SourceViewController” that needs to pass data to a “DestinationViewController.”

import UIKit

class SourceViewController: UIViewController {
// Create a property to store the data you want to pass
var dataToPass: String?

// Function to trigger the presentation of the DestinationViewController
@IBAction func presentDestinationViewController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)

if let destinationVC = storyboard.instantiateViewController(withIdentifier: "DestinationViewController") as? DestinationViewController {
dataToPass = "Hello Universe!!!"
// Set the data property of the DestinationViewController
destinationVC.dataReceived = dataToPass

// Present the DestinationViewController
self.present(destinationVC, animated: true, completion: nil)
}
}
}

Destination ViewController (DestinationViewController):

import UIKit

class DestinationViewController: UIViewController {
// Create a property to receive the data
var dataReceived: String?

// Connect this property to a label or UI element to display the data
@IBOutlet weak var dataLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()

// Check if data has been received
if let data = dataReceived {
// Update the UI element with the received data
dataLabel.text = data
}
}
}

This approach allows you to set data directly to the “DestinationViewController” instance before presenting it. It’s useful when you want to send data that is specific to the presentation of the destination ViewController.

5. Using UserDefaults

For simple data that doesn’t require immediate communication between ViewControllers, you can use UserDefaults to store and retrieve data throughout your app.

Using UserDefaults is a straightforward method for transferring simple data between ViewControllers or persisting data for later use.

Save Data:

UserDefaults.standard.set("Data to pass", forKey: "myDataKey")

Retrieve Data:

if let data = UserDefaults.standard.string(forKey: "myDataKey") {
print("Received data: \(data)")
}

Using UserDefaults for transferring data between view controllers is not recommended.

Conclusion

Transferring data between ViewControllers is a fundamental aspect of iOS app development. The choice of method depends on the complexity of your data and the architecture of your app. By mastering these data-passing techniques in Swift, you’ll be better equipped to create dynamic and interactive iOS applications that effectively manage and communicate data.

--

--

Vikram Kumar
Vikram Kumar

Written by Vikram Kumar

I am Vikram, a Senior iOS Developer at Matellio Inc. focused on writing clean and efficient code. Complex problem-solver with an analytical and driven mindset.

No responses yet