简体   繁体   中英

Trouble passing data between views using EnvironmentObject in SwiftUI

I'm having trouble passing data through different views with EnvironmentObject . I have a file called LineupDetails that contains the following:

import SwiftUI

class TeamDetails: ObservableObject {
    let characterLimit = 3
    @Published var TeamName: String = "" {
        didSet {
            if TeamName.count > characterLimit {
                TeamName = String(TeamName.prefix(characterLimit))
            }
        }
    }
    @Published var TeamColor: Color = .blue
    @Published var hitter1First: String = ""
    @Published var hitter1Last: String = ""
    @Published var hitter2First: String = ""
    @Published var hitter2Last: String = ""
    @Published var hitter3First: String = ""
    @Published var hitter3Last: String = ""
    @Published var hitter4First: String = ""
    @Published var hitter4Last: String = ""
    @Published var hitter5First: String = ""
    @Published var hitter5Last: String = ""
    @Published var hitter6First: String = ""
    @Published var hitter6Last: String = ""
    @Published var hitter7First: String = ""
    @Published var hitter7Last: String = ""
    @Published var hitter8First: String = ""
    @Published var hitter8Last: String = ""
    @Published var hitter9First: String = ""
    @Published var hitter9Last: String = ""
    
    @Published var Hitter1Pos = "P"
    @Published var Hitter2Pos = "C"
    @Published var Hitter3Pos = "1B"
    @Published var Hitter4Pos = "2B"
    @Published var Hitter5Pos = "3B"
    @Published var Hitter6Pos = "SS"
    @Published var Hitter7Pos = "LF"
    @Published var Hitter8Pos = "CF"
    @Published var Hitter9Pos = "RF"
    
}

These variables are edited through a form in SetHomeLineup . I have excluded the parts of the view not related to the problem, marking them with ... :

struct SetHomeLineup: View {
    @EnvironmentObject var HomeTeam: TeamDetails

...
    
var body: some View {
    HStack {
            TextField("Name", text: $HomeTeam.hitter1First)
            TextField("Last Name", text: $HomeTeam.hitter1Last)
                    Picker(selection: $HomeTeam.Hitter1Pos, label: Text("")) {
                            ForEach(positions, id: \.self) {
                                    Text($0)
                    }
            }
    }
    HStack {
            TextField("Name", text: $HomeTeam.hitter2First)
            TextField("Last Name", text: $HomeTeam.hitter2Last)
                    Picker(selection: $HomeTeam.Hitter2Pos, label: Text("")) {
                            ForEach(positions, id: \.self) {
                                    Text($0)
                    }
            }
    }
        ...
}
// and textfields so on until Hitter9, first and last

Now, when I try to include the inputted values of the above text fields to a different view, with code like this, the view always appears empty to match the default value of the string.

struct GameView: View {
    @EnvironmentObject var HomeTeam: TeamDetails
    
    ...
    
    var body: some View {
        Text(HomeTeam.hitter1First)
    }
}

Can someone tell me what I'm doing wrong? I tried using a similar code in a fresh project and it seemed to work just fine, so I'm stumped.

EDIT:

My views are instantiated like so: The first view of the app is the SetAwayLineup, which includes a NavigationLink to SetHomeLineup like so.

var details = TeamDetails()
NavigationLink(destination: SetHomeLineup().environmentObject(details), isActive: self.$lineupIsReady) {

Similarly, SetHomeLineup includes a navigation link to GameView like so

var details = TeamDetails()
NavigationLink(destination: GameView().environmentObject(details), isActive: self.$lineupIsReady) {

Both screens have an EnvironmentObject of AwayLineup and HomeLineup that I'm trying to call into GameView.

Hopefully this simplifies it

The trunk is injected in the NavigationView and doesn't need to be re-injected. Even if one of the children doesn't use it. Truck belongs to the NavigationView

Then I have created 2 branches A and B that have their own Objects A cannot see B s and vicecersa.

Each branch has access to their object and the sub-branches (NavigationLink) can be connected to the branch's object by injecting it.

import SwiftUI

struct TreeView: View {
    @StateObject var trunk: Trunk = Trunk()
    var body: some View {
        NavigationView{
            List{
                NavigationLink(
                    destination: BranchAView(),
                    label: {
                        Text("BranchA")
                    })
                NavigationLink(
                    destination: BranchBView(),
                    label: {
                        Text("BranchB")
                    })
                
            }.navigationTitle("Trunk")
        }
        //Available to all items in the NavigationView
        //With no need to re-inject for all items of the navView
        .environmentObject(trunk)
        
    }
}
///Has no access to BranchB
struct BranchAView: View {
    @StateObject var branchA: BranchA = BranchA()
    @EnvironmentObject var trunk: Trunk
    var body: some View {
        VStack{
            Text(trunk.title)
            Text(branchA.title)
            NavigationLink(
                destination: BranchAAView()
                    //Initial injection
                    .environmentObject(branchA)
                ,
                label: {
                    Text("Go to Branch AA")
                })
        }.navigationTitle("BranchA")
    }
}
//Has no access to BranchA
struct BranchBView: View {
    @StateObject var branchB: BranchB = BranchB()
    @EnvironmentObject var trunk: Trunk
    var body: some View {
        VStack{
            Text(trunk.title)
            Text(branchB.title)
            NavigationLink(
                destination: BranchBBView()
                    //Initial injection
                    .environmentObject(branchB),
                label: {
                    Text("Go to Branch BB")
                })
        }.navigationTitle("BranchB")
    }
}
struct BranchAAView: View {
    @EnvironmentObject var branchA: BranchA
    @EnvironmentObject var trunk: Trunk
    var body: some View {
        VStack{
            Text(trunk.title)
            Text(branchA.title)
            NavigationLink(
                destination: BranchAAAView()
                    //Needs re-injection because it is a NavigationLink sub-branch
                    .environmentObject(branchA)
                ,
                label: {
                    Text("Go to AAA")
                })
        }.navigationTitle("BranchAA")
    }
}
struct BranchAAAView: View {
    @EnvironmentObject var branchA: BranchA
    @EnvironmentObject var trunk: Trunk
    var body: some View {
        VStack{
            Text(trunk.title)
            Text(branchA.title)
        }.navigationTitle("BranchAAA")
    }
}
struct BranchBBView: View {
    var body: some View {
        VStack{
            Text("I don't need to use the trunk or branch BB")
            //No need to re-inject it is the same branch
            BranchBBBView()
        }.navigationTitle("BranchBB")
    }
}
struct BranchBBBView: View {
    @EnvironmentObject var branchB: BranchB
    @EnvironmentObject var trunk: Trunk
    var body: some View {
        VStack{
            Text("BranchBBBView").font(.title).fontWeight(.bold)
            Text(trunk.title)
            Text(branchB.title)
        }.navigationTitle("BranchBB & BranchBBB")
    }
}
struct TreeView_Previews: PreviewProvider {
    static var previews: some View {
        TreeView()
    }
}
class Trunk: ObservableObject {
    var title: String = "Trunk"
}
class BranchA: ObservableObject {
    var title: String = "BranchA"
}
class BranchB: ObservableObject {
    var title: String = "BranchB"
}

You need to make sure that you're passing the same environment object to any views that need to share the same data. That means you should create the object and store it in a variable so that you can pass it to both views. From your comments you have:

NavigationLink(destination: GameView(testUIView: 
    sampleGameViews[0]).environmentObject(TeamDetails()), 
                                          isActive: self.$lineupIsReady { ...

That TeamDetails() constructs a new instance of the TeamDetails class, one that you haven't stored. That means you must also be doing the same for your SetHomeLineup view. Instead, you'll need to create a single instance and keep a reference to it, then pass that reference to any views that you want to share the same data:

var details = TeamDetails()

that should be the only place where you use TeamDetails() ; use details when you're setting up your GameView and SetHomeLineup views.

Update: Given your edit, the problem is again clear. You're still instantiating the TeamDetails class twice, so that the two views still get their own separate instances of that class. They need to use the same instance if they're to share information. So there should be only one var details = TeamDetails() line, and the resulting details variable should be used as the environmentObject for both views.

For example, instead of:

var details = TeamDetails()
NavigationLink(destination: SetHomeLineup().environmentObject(details), isActive: self.$lineupIsReady) {...
//...
var details = TeamDetails()
NavigationLink(destination: GameView().environmentObject(details), isActive: self.$lineupIsReady) {

you want:

var details = TeamDetails()
NavigationLink(destination: SetHomeLineup().environmentObject(details), isActive: self.$lineupIsReady) {...

NavigationLink(destination: GameView().environmentObject(details), isActive: self.$lineupIsReady) {

In general, make sure that you have a clear understanding of the difference between reference types and value types, and between a class and an instance of that class. If Mazda Miata is a class, and if I buy a Miata, then the specific car that I've is an instance of the class. If I ask you to wash my car, and I ask someone else to change the oil in my car, I'll end up with one car that's clean and has new oil. What's going on in your code is that you're buying a Miata and asking someone to wash it, and buying another car and asking someone to change the oil. They're two different cars, so they have different states.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM