@EnvironmentObject doesn't work well through navigationLink
Asked Answered
C

2

12

I use my @EnvironmentObject to let my TeamData can use by different but it doesn't work well. When I go to another view by navigationLink in List, it's fine. When I tap a button to the same View, it crashed..
I think I put something wrong. Can somebody help for this issue? Is it mean I need to add @EnvironmentObject at the NavigationView first so that I can use it? Thanks

Demo: https://youtu.be/-iRadrHd94c

import SwiftUI

struct GameRecordListView: View {
@EnvironmentObject var teamResult : TeamResult

@State var hint : String = ""
@State var successRegistration : Bool = false
@State var sheetPresented: Bool = false
var body: some View {
     VStack{

        List(0 ..< 12){ item in
            NavigationLink(destination: GameDetailRecordView()){   <-this works well
                Text("444")

            }
        }
        VStack{
            Spacer()
            HStack{
                Spacer()
                NavigationLink(destination: GameDetailRecordView())  <-this doesn't works well and crashed
                {
                    Image(systemName: "pencil.circle")
                        .resizable()
                        .frame(width: 35, height: 35)
                        .padding(12)
                        .background(Color.blue)
                        .foregroundColor(Color.white)
                        .clipShape(Circle())

                }
            }.padding(15)

        }

     }.onAppear(perform: {
        print("teamResult:\(self.teamResult.groupID):\(self.teamResult.groupName)")
     })
}
}

I create a enviromentObject in this View

import SwiftUI
import SwiftyJSON

struct TeamDetail: View {
@EnvironmentObject var teamResult : TeamResult
//    @ObservedObject var teamResult: TeamResult
var roles = ["GameRecord", "Schedule", "Money", "Member"]
@State var show = false
@State private var selectedIndex = 0
var body: some View {
    ZStack{
        VStack {
            Picker(selection: $selectedIndex, label: Text("")) {
                ForEach(0..<roles.count) { (index) in
                    Text(self.roles[index])
                }
            }
            .pickerStyle(SegmentedPickerStyle())

            HStack{
                containedView()

            }
            Spacer()

            }


        }.navigationBarTitle(teamResult.groupName)
        .onAppear(perform:{

        })
}



//select different view by selectedIndex
func containedView() -> AnyView {
   switch selectedIndex {
   case 0:
       return AnyView(
            GameRecordListView().environmentObject(teamResult)   <-create environmentObject here
       )

   case 1:

      return AnyView(Text("")
           .padding(30))


   case 2:
      return AnyView(
        BookkeepingView().environmentObject(teamResult)

    )


   default:
    return AnyView(
        TeamMemberListView().environmentObject(teamResult))
}

}
}

The view I use the @EnviromentObject

import SwiftUI

struct GameDetailRecordView: View {
  @EnvironmentObject var teamResult : TeamResult
  var body: some View {
     Text("ID:\(teamResult.groupID)Name:\(teamResult.groupName)")
  }
}

Error message:

Fatal error: No ObservableObject of type TeamResult found.
A View.environmentObject(_:) for TeamResult may be missing as an ancestor of this view.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Monoceros_Sim/Monoceros-39.4.3/Core/EnvironmentObject.swift, line 55
2020-01-20 01:14:58.466655+0800 Fitness(SwiftUI)[49035:4851345] Fatal error: No ObservableObject of type TeamResult found.
A View.environmentObject(_:) for TeamResult may be missing as an ancestor of this view.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Monoceros_Sim/Monoceros-39.4.3/Core/EnvironmentObject.swift, line 55
Corrinecorrinne answered 19/1, 2020 at 17:34 Comment(0)
T
10

try adding environmentObject on the view in the NavigationLink

 NavigationLink(destination: GameDetailRecordView().environmentObject(teamResult))
Turnstile answered 19/1, 2020 at 18:29 Comment(4)
Hi, I use this method and this can work. But I want to know why the NavigationLink in the list doesn't need to add .environmentObject(teamResult) and another need to add .environmentObject(teamResult). Is it means that NavigationLink create a new environment? I think all View below GameRecordListView().environmentObject(teamResult) can use the @environmentObject teamResult at all.Corrinecorrinne
Hi @frank61003, have you managed to figure out why this happens? I am in the same situation.Expostulatory
It appears that all environmentObjects are not passed down when NavigationLink is "created" dynamically at runtime. In those cases, you have to supply environmentObjects again.Soemba
interesting gotcha...probably a bugMoorfowl
O
6

Is it mean I need to add @EnvironmentObject at the NavigationView first so that I can use it?

Correct, NavigationLinks cannot pass environment objects onto their destinations; only the NavigationView can do that, since it owns the view hierarchy.

So, if somewhere in your code you did something like this:

NavigationView {
  ContentView()
    .environmentObject(teamResult)
}

You need to change it to:

NavigationView {
  ContentView()
}
.environmentObject(teamResult)

Then the ContentView will be able to receive the @EnvironmentObject, as well as any destination view of a NavigationLink therein, without needing to use the environmentObject(_:) view modifier on each destination view.

Opposite answered 13/7, 2023 at 22:20 Comment(1)
This should be the top answer. It accurately addresses the question of how to make an environment object work properly, as an environment object should. If I wanted to pass the environment object explicitly through all my navigation links, I would just make it an explicit @Binding on the view itself.Lycian

© 2022 - 2025 — McMap. All rights reserved.