← Back to archive

Development · SwiftUI

Sharing state with @EnvironmentObject

Inject an ObservableObject into the environment and read it from the descendant views that need it.

Read the Korean original on Velog ↗English translation

Development archive cover

Passing state through several levels can force intermediate views to carry data they never use. An environment object lets a descendant access a shared object without passing it through every initializer.

Passing data between views

@StateObject and @ObservedObject allow views to share an ObservableObject. Explicitly passing that reference works, but intermediate views may need properties solely to forward it to their children.

// Shared data
class EnvironmentViewModel: ObservableObject {
    
    @Published var dataArray: [String] = []
    
    init() {
        getData()
    }
    
    func getData() {
        self.dataArray.append(contentsOf: ["iPhone", "iPad", "iMac"])
    }
}

// First view
struct FirstView: View {
    
    // Create data
    @StateObject var viewModel: EnvironmentViewModel = EnvironmentViewModel()
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.dataArray, id: \.self) { item in
                    NavigationLink {
                        SecondView(viewModel: viewModel, selectedItem: item)
                    } label: {
                        Text(item)
                    }
                }
            }
        }
    }
}

// Second view
struct SecondView: View {
    
    // Forward data through an intermediate view
    @ObservedObject var viewModel: EnvironmentViewModel
    let selectedItem: String
    
    var body: some View {
        NavigationLink {
            ThirdView(viewModel: viewModel)
        } label: {
            Text(selectedItem)
        }
    }
}

// Third view
struct ThirdView: View {
    
    // Share the data
    @ObservedObject var viewModel: EnvironmentViewModel

    var body: some View {
        ForEach(viewModel.dataArray, id: \.self) { item in
            Text(item)
        }
    }
}

@EnvironmentObject

A parent can own an object with @StateObject and inject it using .environmentObject(). Descendants read the shared instance with @EnvironmentObject, without requiring every intermediate view to pass it along.

struct FirstView: View {
    
    // Create data
    @StateObject var viewModel: EnvironmentViewModel = EnvironmentViewModel()
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.dataArray, id: \.self) { item in
                    NavigationLink {
                        SecondView(selectedItem: item)
                    } label: {
                        Text(item)
                    }
                }
            }
        }
        // Share the object with descendants
        .environmentObject(viewModel)
    }
}

struct SecondView: View {
    
    // No intermediate forwarding needed
    let selectedItem: String
    
    var body: some View {
        NavigationLink {
            ThirdView()
        } label: {
            Text(selectedItem)
        }
    }
}

struct ThirdView: View {
    
    // Share the data
    @EnvironmentObject var viewModel: EnvironmentViewModel
    
    var body: some View {
        ForEach(viewModel.dataArray, id: \.self) { item in
            Text(item)
        }
    }
}

← Back to archive