How to Pass Data Between Views in SwiftUI
Learn how to pass data between views in SwiftUI using bindings, environment objects, and more.
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
- ContentView defines a
@Statevariablenamewhich holds the input value. - The
TextFieldbinds to this state using$name, allowing the user to update it. NavigationLinkpushes aDetailView, passing the binding tonameas a parameter.- DetailView uses
@Bindingto reflect changes in the name from the parent view. - Any change in the
namein 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
- 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.
- Overusing @Binding in deep hierarchies – Passing bindings through multiple layers can get messy. Prefer
@EnvironmentObjector combine it with@ObservedObjectfor better scalability. - Mutating State Incorrectly – Using non-
@Publishedproperties inObservableObjectwon't trigger view updates. Always use@Publishedfor properties you want to bind to views.
Related Guides
- how-to-use-environmentobject
- how-to-share-data-across-views
- how-to-manage-state-with-observableobject