iOS
Swift
prepareForSegue
Navigation Controller
Data Passing

Set data in prepareForSegue with navigation controller

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

When prepare(for:sender:) involves a navigation controller, the main trick is understanding what segue.destination actually is. If the destination scene is embedded in a UINavigationController, you do not set data on the navigation controller itself. You unwrap its visible or top view controller and pass data to that real content controller.

Start with the normal prepare(for:sender:) pattern

For a direct segue to a detail controller, the code is simple:

swift
1override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
2    if segue.identifier == "showDetail",
3       let detail = segue.destination as? DetailViewController {
4        detail.itemID = 42
5    }
6}

That works when the destination is the actual DetailViewController.

Embedded navigation controllers change the destination type

If the storyboard segue goes to a navigation controller whose root view controller is DetailViewController, then this cast fails:

swift
segue.destination as? DetailViewController

The real destination is a UINavigationController, so you need to unwrap it:

swift
1override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
2    if segue.identifier == "showDetail",
3       let nav = segue.destination as? UINavigationController,
4       let detail = nav.topViewController as? DetailViewController {
5        detail.itemID = 42
6    }
7}

This is a very common pattern for modal flows where the presented screen has its own navigation stack.

Choose topViewController or visibleViewController deliberately

In many cases, topViewController is enough because the navigation controller has not started navigating yet. visibleViewController can also work, but the important part is to reach the actual screen controller that owns the data property.

For a root controller that is embedded at presentation time, either of these is often fine:

  • 'nav.topViewController'
  • 'nav.visibleViewController'

Use the one that matches your storyboard structure and make the cast explicit.

Keep segue identifiers explicit

As screens grow, prepare(for:sender:) becomes hard to reason about if every segue uses untyped branching. Naming the segue and checking its identifier keeps the method predictable:

swift
1override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
2    switch segue.identifier {
3    case "showDetail":
4        if let nav = segue.destination as? UINavigationController,
5           let detail = nav.topViewController as? DetailViewController {
6            detail.itemID = 42
7        }
8    default:
9        break
10    }
11}

That structure scales better than guessing destination types without checking which segue fired.

Push segues usually do not need navigation unwrapping

If you are pushing onto an existing navigation stack, segue.destination is often already the destination content controller, not a new UINavigationController.

So the navigation-controller unwrap is usually needed when:

  • presenting a modal flow embedded in a nav controller
  • using a storyboard scene whose initial view controller is a nav controller

It is usually not needed for a standard push from one content controller to another.

Prefer direct property assignment over reaching through later

prepare(for:sender:) is the right time to set simple destination properties such as IDs, selected models, or flags. It keeps the data handoff near the navigation event instead of scattering it through lifecycle methods later.

If the dependency graph becomes complicated, use dependency injection patterns or coordinators, but for straightforward storyboard flows, direct property assignment is still fine.

Common Pitfalls

  • Casting segue.destination directly to the content controller when it is actually a UINavigationController.
  • Setting data on the navigation controller instead of the child view controller that needs it.
  • Forgetting to check the segue identifier before unwrapping destination types.
  • Using navigation-controller unwrapping for ordinary push segues where it is not needed.
  • Assuming prepare(for:sender:) runs after the destination has already loaded all of its UI state.

Summary

  • 'prepare(for:sender:) passes data to the destination before the transition completes.'
  • If the destination scene is embedded in a UINavigationController, unwrap it first.
  • Set data on the child content controller, usually via topViewController or visibleViewController.
  • Use segue identifiers so destination-type logic stays explicit.
  • Standard push segues often do not require navigation-controller unwrapping.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.