Passing Data Between Screens in iOS: From Beginner to Pro
\n\nEveryone who starts writing for iOS eventually hits a wall: "How do I pass a string, array, or object to another screen?" This question is so popular that on Stack Overflow it has gathered thousands of views and dozens of answer options. And for good reason — iOS architecture is built so that each screen (ViewController) lives its own life, and data between them needs to be passed deliberately.
\n\nIn this article, we will break down 5 main ways to pass data between view controllers in Swift. You will learn when to use a segue, and when to use delegates or closures, and why NotificationCenter is not always evil. Let's go!
\n\nWhy is this even a problem?
\niOS uses a strict MVC (Model-View-Controller) architecture. Each ViewController is responsible for its own piece of the screen and does not have direct access to the data of a neighboring controller. This is done for safety and modularity, but for the developer, it means you need to explicitly "pass" data from point A to point B.
\n\nImagine: you have a list of users, and you want to show detailed information when a cell is tapped. You tap — a new screen opens. How do you pass the user ID? This is where the magic begins.
\n\nMethod 1: Passing Data via Storyboard Segue (Simplest)
\nThe most straightforward way is to use prepare(for:sender:). You simply prepare the data before the transition. Ideal for simple apps and prototypes.
// In the source ViewController\noverride func prepare(for segue: UIStoryboardSegue, sender: Any?) {\n if segue.identifier == "showDetail" {\n guard let destinationVC = segue.destination as? DetailViewController else { return }\n destinationVC.userID = 42 // passing a number\n destinationVC.userName = "Anna" // passing a string\n }\n}\n\n// In DetailViewController\nvar userID: Int = 0\nvar userName: String = ""\n\n\nPros: Minimal code, everything in one place. Cons: Tight coupling to Storyboard, hard to test, easy to get confused with identifiers.
\n\nMethod 2: Programmatic Navigation with Data Passing (Advanced Level)
\nWhen you get tired of segues and want to control everything from code, use a direct controller instance.
\n\n// In the source ViewController\nfunc showDetail() {\n let storyboard = UIStoryboard(name: "Main", bundle: nil)\n guard let detailVC = storyboard.instantiateViewController(withIdentifier: "DetailVC") as? DetailViewController else { return }\n \n detailVC.userID = 42\n detailVC.userName = "Anna"\n \n navigationController?.pushViewController(detailVC, animated: true)\n}\n\n\nThis method gives you full control: you can load data from the network, validate it, and only then transition. Great for complex logic.
\n\nMethod 3: Delegates (Delegate Pattern) — Callback
\nWhat to do if you need to pass data back? For example, a user selected a date on the second screen, and we want to return it to the first. This is where the delegate comes to the rescue.
\n\nStep 1: Create a Protocol
\nprotocol DatePickerDelegate: AnyObject {\n func didSelectDate(_ date: Date)\n}\n\n\nStep 2: Add a Delegate to the Child Controller
\nclass DatePickerViewController: UIViewController {\n weak var delegate: DatePickerDelegate?\n \n @IBAction func saveButtonTapped(_ sender: UIButton) {\n let selectedDate = datePicker.date\n delegate?.didSelectDate(selectedDate)\n dismiss(animated: true)\n }\n}\n\n\nStep 3: Implement the Delegate in the Parent Controller
\nclass MainViewController: UIViewController, DatePickerDelegate {\n func didSelectDate(_ date: Date) {\n print("Received date: \\(date)")\n // update UI\n }\n \n override f