I am trying to build an app store mock up using swift UI. The app is supposed to pull data from firebase and display it in a list of cards. After tapping the card, it will show the detail of the card. But when I try to build the code, this error appears.
Thread 1: Fatal error: No ObservableObject of type DetailPageViewModel found.
A View.environmentObject(_:) for DetailPageViewModel may be missing as an ancestor of this view.
Before I added the transition logic, I was using @ObservedObject wrapper on the viewModel object, however the transition logic did not work with @ObservedObject. So I thought I must messed up with the passing of the viewModel. So I change it to the @EnvironmentObject, and added .environmentObject(detailObject) to the top-level view. Then the fatal error appears.
The tab bar view:
struct TabBar: View {
@Namespace var animation
@StateObject var detailObject = DetailPageViewModel()
var body: some View {
ZStack {
TabView {
//Home Page
HomePage(animation: animation)
.environmentObject(detailObject)
.tabItem({
Image(systemName: "house")
Text("Home")
})
//Search Page
SearchPage()
.tabItem({
Image(systemName: "magnifyingglass")
Text("Search")
})
}
if detailObject.show {
DetailPage(detailPageViewModel: detailObject, animation: animation)
}
}
}
}
Homepage view:
struct HomePage: View {
//firebase
//@ObservedObject var detailPageViewModel = DetailPageViewModel()
var animation: Namespace.ID
@EnvironmentObject var detailPageViewModel : DetailPageViewModel
var body: some View {
ScrollView(.vertical, showsIndicators: true){
Spacer()
//something something
LazyVStack (alignment: .leading) {
ForEach (detailPageViewModel.HomepageList){ recipeDets in
HomePageCard(recipeDetail: recipeDets, animation: animation)
.onTapGesture {
print(detailPageViewModel.show)
withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.8)){
detailPageViewModel.selectedRecipe = recipeDets
detailPageViewModel.show.toggle()
}
print(detailPageViewModel.show)
}
}
}
}//.background(Color.primary.opacity(0.06).ignoresSafeArea())
}
init(animation: Namespace.ID) {
self.animation = animation
detailPageViewModel.getData() //This is where the error pops up
}
}
DetailPage View:
struct DetailPage: View {
@ObservedObject var detailPageViewModel : DetailPageViewModel
var animation: Namespace.ID
var body: some View {
ScrollView {
ZStack {
LazyImage(url: URL(string: detailPageViewModel.selectedRecipe.fullImagePath), resizingMode: .aspectFill)
.aspectRatio(contentMode: .fit)
.matchedGeometryEffect(id: detailPageViewModel.selectedRecipe.fullImagePath, in: animation)
.ignoresSafeArea()
}
}.ignoresSafeArea(.all, edges: .top)
.statusBar(hidden: true)
}
}
DetailPageViewModel:
@Published var selectedRecipe = RecipeDetail(...)
@Published var show = false
@Published var HomepageList = [RecipeDetail]()
func getData() {
let db = Firestore.firestore()
db.collection("Recipe").getDocuments { snapshot, error in
// check for errors
if error == nil {
// No errors
print("no error")
if let snapshot = snapshot {
//Update the list in main thread
DispatchQueue.main.async {
// get all the documents and create Recipe list
self.HomepageList = snapshot.documents.map { recipeInfo in
return RecipeDetail(....)
}
}
}
} else {
// Handle the error
}
}
}
}
Edit: Edit the question format. Edit: Added comment to the faulty line.
CodePudding user response:
Do not use EnvironmentObject
in the init()
, use it instead in the .onAppear {...}
. Try this example code:
struct HomePage: View {
var animation: Namespace.ID
@EnvironmentObject var detailPageViewModel : DetailPageViewModel
var body: some View {
ScrollView(.vertical, showsIndicators: true){
Spacer()
//something something
LazyVStack (alignment: .leading) {
ForEach (detailPageViewModel.HomepageList){ recipeDets in
HomePageCard(recipeDetail: recipeDets, animation: animation)
.onTapGesture {
print(detailPageViewModel.show)
withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.8)){
detailPageViewModel.selectedRecipe = recipeDets
detailPageViewModel.show.toggle()
}
print(detailPageViewModel.show)
}
}
}
}//.background(Color.primary.opacity(0.06).ignoresSafeArea())
.onAppear {
detailPageViewModel.getData() // <-- here
}
}
init(animation: Namespace.ID) {
self.animation = animation
}
}