Reputation: 49
I was learning about how EnvironmentObject
works for a school project, and I was confused about how to instantiate a view with multiple EnvironmentObjects. For example, the following code:
import SwiftUI
class names: ObservableObject {
@Published var myName = ""
}
struct FirstView: View {
@StateObject var FirstName = names()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $FirstName.myName)
NavigationLink(destination: SecondView()) {
Text("Second View")
}
}
}.environmentObject(FirstName)
}
}
struct SecondView: View {
@StateObject var LastName = names()
var body: some View {
VStack {
TextField("Type", text: $LastName.myName)
NavigationLink(destination: ThirdView().environmentObject(FirstName).environmentObject(LastName)) {
Text("Third View")
}
}.environmentObject(LastName)
}
}
struct ThirdView: View {
@EnvironmentObject var FirstName: names
@EnvironmentObject var LastName: names
var body: some View {
Text("Full name: \(FirstName.myName) \(LastName.myName)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
I need ThirdView
to receive FirstName
from FirstView
and LastName
from SecondView
, but I can't instantiate ThirdView
from SecondView
with the required Environment Objects; this code above crashes with the error "Cannot find FirstName in scope".
Alternatively, If I try to instantiate ThirdView with only LastName
as an environment object, the code will present something like "Smith Smith" if I entered "John" in the text field on FirstView
and "Smith" in the text field on SecondView
.
Can someone tell me what I'm doing wrong? Thank you! :)
Upvotes: 0
Views: 227
Reputation: 4785
You are probably looking for EnvironmentKey
s.
Use them like this:
private struct FirstNameKey: EnvironmentKey {
static let defaultValue = "No first name"
}
private struct LastNameKey: EnvironmentKey {
static let defaultValue = "No last name"
}
And add them to your EnvironmentValues:
extension EnvironmentValues {
var firstName: String {
get { self[FirstNameKey.self] }
set { self[FirstNameKey.self] = newValue }
}
var lastName: String {
get { self[LastNameKey.self] }
set { self[LastNameKey.self] = newValue }
}
}
They values can then be bound to the environment like this:
var body: some View {
MyCustomView()
.environment(\.firstName, "John")
.environment(\.lastName, "Doe")
}
And retrieved like this:
struct ThirdView: View {
@Environment(\.firstName) var firstName
@Environment(\.lastName) var lastName
var body: some View {
Text("Full name: \(firstName) \(lastName)")
}
}
To understand code more easily the Swift.org community asks to
Give types UpperCamelCase names (such as SomeStructure and SomeClass here) to match the capitalization of standard Swift types (such as String, Int, and Bool). Give properties and methods lowerCamelCase names (such as frameRate and incrementCount) to differentiate them from type names.
So it would be better to write your class names
as class Names
as it greatly improves readability for Swift users.
Upvotes: 1
Reputation: 29242
Since they are of the same type you can’t have two. SwiftUI can’t tell the difference
//Names for classes and structs should start with an uppercase letter
class PersonModel: ObservableObject {
@Published var firstName = ""
@Published var lastName = ""
}
struct FirstNameView: View {
//variables start with lowercase
@StateObject var person: PersonModel = PersonModel()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $person.firstName)
NavigationLink(destination: LastNameView()) {
Text("Second View")
}
}
}.environmentObject(person)
}
}
struct LastNameView: View {
@EnvironmentObject var person: PersonModel
var body: some View {
VStack {
TextField("Type", text: $person.lastName)
NavigationLink(destination: FullNameView()) {
Text("Third View")
}
}
}
}
struct FullNameView: View {
@EnvironmentObject var person: PersonModel
var body: some View {
Text("Full name: \(person.firstName) \(person.lastName)")
}
}
Upvotes: 2
Reputation: 816
environmentObject(_:) modifier method takes an ObservableObject and passes it down the view tree. It works without specifying an environment key because the type of the object is automatically used as the key.
So to resume your last name instance is somehow invalidating your first name instance.
I'd then suggest either to create a model that contains both first and last name or simply use @Environment with a key (as it's suggested by Damiaan Dufaux
) if it’s possible to get away with passing a value type, because it’s the safer mechanism.
Upvotes: 1