I get the error Fatal error: No ObservableObject of type RegistrationViewModel found. A View.environmentObject(_:) for RegistrationViewModel may be missing as an ancestor of this view.
struct ContentView: View {
@StateObject var spotifyData = SpotifyDataManager()
@StateObject var spotify = SpotifyAuthManager()
@AppStorage("signedIn") var isSignedIn: Bool = false
@State private var selection: Int = 0
@StateObject var viewModel = ContentViewModel()
@StateObject var regViewModel = RegistrationViewModel()
var body: some View {
NavigationStack {
Group {
if viewModel.userSession == nil {
SignInOptionsView()
.environmentObject(regViewModel)
} else {
VStack {
//If the user is signed, the tab view is shown
if isSignedIn {
TabView(selection: $selection) {
HomeView()
.tabItem {
if selection == 0 {
Image(systemName: "house.fill")
} else {
Image(systemName: "house")
.environment(\.symbolVariants, .none)
}
}.tag(0)
SearchView()
.tabItem {
Image(systemName: "magnifyingglass")
}.tag(1)
// CreateView()
// .tabItem {
// if selection == 2 {
// Image(systemName: "plus.app.fill")
// } else {
// Image(systemName: "plus.app")
// .environment(\.symbolVariants, .none)
// }
// }.tag(2)
// TrendingView()
// .tabItem {
// Image(systemName: "chart.line.uptrend.xyaxis")
// }.tag(3)
ProfileView()
.tabItem {
if selection == 4 {
Image(systemName: "person.circle.fill")
} else {
Image(systemName: "person.circle")
.environment(\.symbolVariants, .none)
}
}.tag(2)
}
} else {
//shows the signin view if the user is not signed in
SignInView()
}
}
.accentColor(.primary)
.onOpenURL { url in
Task {
await spotify.HandleURLCode(url)
}
}
//this requests a new token when needed
.task {
if SpotifyAuthManager.shouldRefresh {
try? await SpotifyAuthManager.getRefreshedAccessToken()
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Text("JamaGram")
.foregroundColor(.primary)
.font(.system(size: 25))
}
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink {
Text("Notifications")
} label: {
Image(systemName: "bell")
.foregroundColor(.primary)
.font(.system(size: 20))
}
}
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink {
CreateView()
} label: {
Image(systemName: "plus.app")
.foregroundColor(.primary)
.font(.system(size: 20))
}
}
// ToolbarItem(placement: .navigationBarTrailing) {
// NavigationLink {
// Text("Messages")
// } label: {
// Image(systemName: "message")
// .foregroundColor(.primary)
// .font(.system(size: 20))
// }
// }
}
}
}
}
}
}
The parent view
struct SignInOptionsView: View {
var body: some View {
NavigationStack {
VStack {
Spacer()
Text("JamaGram")
.foregroundColor(.primary)
.font(.largeTitle)
.padding(.vertical)
Text("Welcome to JamaGram! The best place to share your favorite music with friends.")
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.font(.headline)
VStack(spacing: 30) {
NavigationLink {
SignInView()
.navigationBarBackButtonHidden()
} label: {
LargeButtonView(title: "Sign in with Email")
}
NavigationLink {
SignInView()
} label: {
LargeButtonView(title: "Sign in with Apple")
}
NavigationLink {
SignInView()
} label: {
LargeButtonView(title: "Sign in with Google")
}
}
.padding(.vertical)
Spacer()
NavigationLink {
AddEmailView()
.navigationBarBackButtonHidden()
} label: {
Text("Don't have an account? Sign up now!")
.foregroundColor(Color("MainColor"))
.padding(.vertical)
.font(.footnote)
}
}
.padding(.vertical)
.padding(.horizontal, 18)
}
}
}
The child view where the error occurs
struct AddEmailView: View {
@Environment(\.dismiss) var dismiss
@EnvironmentObject var viewModel: RegistrationViewModel
var body: some View {
VStack(spacing: 25) {
Spacer()
Text("Enter your email address")
.foregroundColor(.primary)
.font(.title)
.multilineTextAlignment(.center)
Text("This email address will be used to log in to your account. It will not be visible to others.")
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.font(.headline)
.padding(.horizontal, 18)
TextField("Email address", text: $viewModel.email)
.modifier(TextFieldModifier())
.autocapitalization(.none)
.keyboardType(.emailAddress)
NavigationLink {
AddPasswordView()
.navigationBarBackButtonHidden()
} label: {
LargeButtonView(title: "Next")
}
Spacer()
Text("1/4")
.foregroundColor(.secondary)
.font(.footnote)
.fontWeight(.semibold)
.padding(.vertical)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
dismiss()
} label: {
Image(systemName: "chevron.left")
.foregroundColor(.primary)
.font(.system(size: 20))
}
}
}
}
}
I have seen similar issues where people have been using sheets with environment objects but that is not the case in my code so I'm not sure what's causing the issue.
I tried to inject the environmentObject on the SignInOptionsView which I hoped would share the object with the addEmailView but at pressing the NavigationLink that says don't have an account? I get the error Fatal error: No ObservableObject of type RegistrationViewModel found. A View.environmentObject(_:) for RegistrationViewModel may be missing as an ancestor of this view.
SwiftUI.TabView
the tab view must always be at the top, each tab can have its own NavigationStack
but a tab view cannot be inside a of one.Tab bars use bar items to navigate between mutually exclusive panes of content in the same view.
https://developer.apple.com/design/human-interface-guidelines/tab-bars
TabView > NavigationStack-tabItem
NOT
NavigationStack > TabView > NavigationStack-tabItem
environmentObject()
must be injected onto the parent, in this case the NavigationStack
if the other tabs don't need it or the TabView
if multiple tabs need it.TabView {
}.environmentObject(regViewModel)
or
TabView {
NavigationStack {
}
.environmentObject(regViewModel)
.tabItem {
}
}
Here is another link that can help.