I am creating an iOS app using XCode, and I'm having some problems getting an EnvironmentObject
to transfer correctly between my main view and a child view. They are linked by a NavigationLink
inside of a NavigationStack
. Here is a simplified version of my code that only contains the necessary code:
struct MainPageView: View {
@EnvironmentObject var groups: groupManager
var body: some View {
NavigationStack {
// omitted code
NavigationLink {
AccountView()
} label: {
VStack {
if account.username.isEmpty {
Image("default_account_image")
} else {
account.profilePicture
}
Text("Account")
}
}
// omitted code
}
}
}
Styling is also removed from the code, so if you try to preview it, it will look really weird. account
is another environment variable, but it's not related in any way. The groups
object is what I am having trouble with. When I navigate to AccountView
it should show me a list of groups that the user is a part of, but for some reason it just shows like this blank box. Here is the definition of 'groupManager':
class groupManager: ObservableObject {
@Published var groups: [Group] = getAllGroups()
}
It is not a problem with the getAllGroups
method and I have checked this with print debugging. It's also not a problem with the AccountView
as far as I can tell. If I view the list of groups from AccountView
instead of my main view, the list loads correctly. Here is the code for AccountView
:
struct AccountView: View {
@State private var tab: Int = 0
@EnvironmentObject var groups: groupManager
var body: some View {
VStack {
Picker("Tabs", selection: $tab) {
Text("Personal").tag(0)
Text("Groups").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
if tab == 0 {
Text("Showing personal view")
PersonalView()
} else if tab == 1 {
Text("Showing group view")
GroupList()
}
}
}
}
The same groups
environment object is passed to the view, and this time it loads correctly. When going to the GroupList
tab, it correctly displays the list of groups that the user is a part of. Here is an example photo of what it should look like:
Preview from XCode of what it's supposed to look like
Here is what it looks like when I look at it from the main view instead: Preview from XCode of what it looks like from the main view
The last piece of information that I think (and hope) is relevant, is what the GroupList
view looks like. It's a typical list that I learned how to make from the Apple XCode docs, and here it is:
struct GroupList: View {
@EnvironmentObject var groups: groupManager
var body: some View {
NavigationSplitView {
List(groups.groups) {group in
NavigationLink {
GroupRecipeList(group: group)
} label: {
GroupRow(group: group)
}
.listRowBackground(Color(red: 0.92, green: 0.92, blue: 0.92))
}
.scrollContentBackground(.hidden)
} detail: {
Text("Select a group")
}
}
}
Here we can see the same groups
environment object. To be clear, this object is passed to the main view in the app's entry point for when it is run not on simulation.
Edit: Here is the injection point and creation of the environment objects as well as asked by the comments:
@main
struct Recipe_BookApp: App {
@StateObject var userGroups = groupManager()
var body: some Scene {
WindowGroup {
MainPageView()
.environmentObject(userGroups)
}
}
That should be all the background. So far I have tried just about everything I can think of. I tried making it just a variable defined in another file instead of an EnvironmentObject
, but I still had the same error. I tried explicitly passing the EnvironmentObject
to AccountView
in the NavigationLink
using .environmentObject()
but that didn't work either. I also tried explicitly passing it to GroupList
from AccountView
. I've tried asking ChatGPT to solve my problems, but it just gave me the same advice really and never actually fixed the problem. I'm all out of ideas at this point, so I would appreciate some help!
Since the issue is with Preview you should add the missing/inherited elements in the Preview
macro
#Preview {
NavigationStack {
ComplexView()
}
// Potentially expensive if `AppState` is large or complex.
.environment(AppState())
}
You can make the code reusable with a PreviewModifier
// Create a struct conforming to the PreviewModifier protocol.
struct SampleData: PreviewModifier {
// Define the object to share and return it as a shared context.
static func makeSharedContext() async throws -> AppState {
let appState = AppState()
appState.expensiveObject = "An expensive object to reuse in previews"
return appState
}
func body(content: Content, context: AppState) -> some View {
// Inject the object into the view to preview.
content
.environment(context)
}
}
// Add the modifier to the preview.
#Preview(traits: .modifier(SampleData())) {
ComplexView()
}