How to Open Different Screens From the First Screen in SwiftUI
Navigation is one of the first real challenges SwiftUI developers face. You've built a starting screen — maybe a home view, a login page, or a dashboard — and now you need to move users to other parts of your app. SwiftUI gives you several tools to do this, and choosing the right one depends on how your app is structured and what kind of experience you're building.
Understanding SwiftUI's Navigation Model
SwiftUI uses a declarative navigation system, meaning you describe what should happen rather than writing imperative commands like "push this view controller." This is a shift from UIKit, and it affects how you think about screen transitions.
At its core, navigation in SwiftUI is driven by state. When a state value changes, the UI reacts — including which screen is visible. This means your navigation logic lives in your data, not scattered across view controllers.
The Main Methods for Moving Between Screens
1. NavigationStack (iOS 16+)
NavigationStack is the modern, recommended approach for stack-based navigation — the kind where users drill down into detail screens and tap a back button to return.
NavigationStack { List(items) { item in NavigationLink(item.name, value: item) } .navigationDestination(for: Item.self) { item in DetailView(item: item) } } The key concept here is navigationDestination, which maps a data type to a destination view. When a NavigationLink is tapped and passes a value of that type, SwiftUI knows exactly which view to show. This data-driven approach makes deep linking and programmatic navigation much cleaner than older methods.
2. NavigationView (iOS 13–15)
If you're supporting older iOS versions, NavigationView with NavigationLink is the equivalent. The syntax is slightly different, and NavigationView has some quirks on iPad, but it works reliably for most phone-first apps.
NavigationView { NavigationLink("Go to Detail") { DetailView() } } Important distinction:NavigationView is deprecated as of iOS 16, so if you're building for current devices, NavigationStack is the better path.
3. Sheet Presentation (Modal Screens)
Not every transition should be a push. Sheets slide up from the bottom and are used for self-contained tasks — settings panels, forms, or contextual actions. They don't imply hierarchy; they imply a temporary detour.
@State private var showingSheet = false Button("Open Settings") { showingSheet = true } .sheet(isPresented: $showingSheet) { SettingsView() } The $showingSheet binding is a Boolean that controls visibility. When the user dismisses the sheet, it flips back to false automatically.
4. Full-Screen Cover
Similar to a sheet, but the new view completely covers the screen with no peek of the content below. Useful for onboarding flows, media playback, or any experience that needs total visual focus.
.fullScreenCover(isPresented: $showingOnboarding) { OnboardingView() } The user can't swipe to dismiss a full-screen cover by default — you need to provide an explicit dismiss action, which gives you more control over the exit flow.
5. TabView for Top-Level Navigation
If your app has multiple peer sections — think a social app with a feed, search, notifications, and profile — TabView is the right structure. Each tab is a separate root screen, not a pushed destination.
TabView { HomeView() .tabItem { Label("Home", systemImage: "house") } ProfileView() .tabItem { Label("Profile", systemImage: "person") } } TabView and NavigationStack are frequently combined: tabs at the top level, with a navigation stack inside each tab for drilling into detail views. 🗂️
Programmatic Navigation
Sometimes you need to navigate without the user tapping a link directly — after a login completes, after a form submits, or based on a push notification.
With NavigationStack, this is handled via a path binding:
@State private var path = NavigationPath() NavigationStack(path: $path) { HomeView() .navigationDestination(for: String.self) { screen in destinationView(for: screen) } } You can then push a new screen by appending to the path:
path.append("detail") Or pop back to the root by clearing it:
path = NavigationPath() This approach works well for apps that need to respond to external events and navigate without direct user input.
Factors That Shape Your Navigation Approach 🔧
Several variables determine which combination of these tools fits your situation:
| Factor | Impact on Navigation Choice |
|---|---|
| Minimum iOS version | iOS 16+ unlocks NavigationStack; older targets require NavigationView |
| App structure | Hierarchical content favors stacks; peer sections favor tabs |
| Task type | Temporary tasks suit sheets; main flows suit push navigation |
| Programmatic needs | Deep linking or post-action redirects need path-based navigation |
| Platform | iPad, macOS, and tvOS have different navigation conventions |
| Team experience | UIKit veterans may need time to adjust to SwiftUI's declarative model |
Nesting and Combining Navigation Patterns
Real apps rarely use just one pattern. A common architecture looks like this:
- TabView at the root with three or four tabs
- NavigationStack inside each tab
- Sheets triggered from within stack views for focused tasks
- Full-screen covers for onboarding or media
The main pitfall is nesting NavigationStack inside NavigationStack, which creates duplicate navigation bars and unpredictable behavior. Each tab should have its own stack, not a stack inside a stack inside another stack.
The Environment Dismiss Pattern
When a view is presented modally (sheet or full-screen cover), it can't pop itself off a navigation stack — it needs to dismiss itself. SwiftUI provides an environment value for this:
@Environment(.dismiss) var dismiss Button("Done") { dismiss() } This keeps views loosely coupled — the presented view doesn't need to know how it was shown in order to close itself. ✅
What This Means for Your App
The mechanics of SwiftUI navigation are consistent, but the right architecture depends on decisions that are specific to your project — the iOS version floor you're targeting, whether your content is hierarchical or parallel, how much programmatic control you need, and whether you're building for iPhone only or across Apple's entire platform family. Each of those variables shifts which patterns belong at the center of your design and which ones play a supporting role.