SwiftUI.cc
Guides
GuideStateBeginner

How to Pass Data Between Views in SwiftUI

Learn how to pass data between views in SwiftUI using bindings, environment objects, and more.

7 min readUpdated 2026-06

Overview

Passing data between views is a fundamental task in SwiftUI for building dynamic and interactive apps. SwiftUI provides several tools like @Binding, @State, and @EnvironmentObject that make data flow intuitive and declarative. This guide will show you how to effectively pass data between views in a SwiftUI app.

The Approach

Here's a basic example demonstrating how to pass data between two views using @Binding and @State:

import SwiftUI

texture struct ContentView: View {
    @State private var name: String = ""

    var body: some View {
        VStack {
            TextField("Enter your name", text: $name)
            NavigationLink("Go to Detail", destination: DetailView(name: $name))
        }
        .padding()
    }
}

struct DetailView: View {
    @Binding var name: String

    var body: some View {
        Text("Hello, $name)")
            .font(.largeTitle)
            .padding()
    }
}

This code uses @State in the parent view to store the name, and @Binding in the child view to allow two-way communication.

Step-by-Step Breakdown

  1. ContentView defines a @State variable name which holds the input value.
  2. The TextField binds to this state using $name, allowing the user to update it.
  3. NavigationLink pushes a DetailView, passing the binding to name as a parameter.
  4. DetailView uses @Binding to reflect changes in the name from the parent view.
  5. Any change in the name in either view updates both automatically, thanks to the binding.

Common Variations

Using @EnvironmentObject

When passing data across multiple layers of views, @EnvironmentObject is often more convenient:

import SwiftUI
class UserData: ObservableObject {
    @Published var name: String = ""
}

texture struct ContentView: View {
    @StateObject private var userData = UserData()

    var body: some View {
        NavigationStack {
            VStack {
                TextField("Enter your name", text: $userData.name)
                NavigationLink("Go to Detail", destination: DetailView())
            }
            .padding()
            .environmentObject(userData)
        }
    }
}

struct DetailView: View {
    @EnvironmentObject private var userData: UserData

    var body: some View {
        Text("Hello, $userData.name)")
            .font(.largeTitle)
            .padding()
    }
}

This approach avoids passing data manually through each view hierarchy and makes the data accessible to any view in the tree.

Using NavigationStack (iOS 16+)

For navigation in iOS 16+, NavigationStack simplifies the process and works well with environment objects:

NavigationStack {
    VStack {
        TextField("Enter your name", text: $userData.name)
        NavigationLink("Go to Detail", value: DetailView())
    }
    .environmentObject(userData)
}

Make sure to set the destination in the navigation stack and the environment object is available in all child views.

Pitfalls to Avoid

  1. Forgetting to initialize @EnvironmentObject – If a view expects an environment object and it’s not provided, the app will crash at runtime. Always ensure the object is injected before use.
  2. Overusing @Binding in deep hierarchies – Passing bindings through multiple layers can get messy. Prefer @EnvironmentObject or combine it with @ObservedObject for better scalability.
  3. Mutating State Incorrectly – Using non-@Published properties in ObservableObject won't trigger view updates. Always use @Published for properties you want to bind to views.
  • how-to-use-environmentobject
  • how-to-share-data-across-views
  • how-to-manage-state-with-observableobject