仅限 Firestore returns 1 项

Firestore only returns 1 item

当我调用 firestore 时,它​​ returns 正确数量的项目,但将它们显示为相同的项目,而不是 2 个单独的项目

例如,我有一份咖喱鸡食谱和一份鲑鱼米饭食谱,但只显示了鲑鱼米饭食谱

这是我的代码,用于显示我返回的数据

import SwiftUI
import SDWebImageSwiftUI

struct CardView: View {
    @EnvironmentObject var model: RecipesViewModel
    var body: some View {
        ForEach(model.Recipes,id: \.id) {item in
            VStack{
                ZStack(alignment: .bottomTrailing){
                    WebImage(url: URL(string: item.imageURL))
                        .resizable()
                        .scaledToFit()
                        .cornerRadius(8)
                        .frame(width: 150, height: 150)
                    HStack{
                        Text("\(item.likes)")
                        
                        Image(systemName: "heart")
                    }
                    .foregroundColor(Color(hex: "FF0044"))
                    .padding(5)
                    .background(.white)
                    .cornerRadius(8)
                    .padding(7)
                    
                }
                
                
                Text(item.name)
                    .foregroundColor(.black)
                    .fontWeight(.medium)
                    .font(.system(size: 20))
                    .padding(.top)
                
                
                HStack{
                    Image(systemName: "clock")
                        .foregroundColor(.gray)
                    Text("\(item.prepMins) mins")
                        .foregroundColor(.gray)
                }
                .padding(5)
                .padding(.trailing)
            }
        }
    }
}

struct CardView_Previews: PreviewProvider {
    static var previews: some View {
        CardView()
        PopularRecipes()
        AllPopular()
    }
}

struct PopularRecipes:View{
    
    @State var presented1:Bool = false
    @StateObject var model = RecipesViewModel()
    
    var body: some View{
        
        VStack{
            HStack{
                Text("Popular")
                    .font(.system(size: 20))
                    .fontWeight(.medium)
                Spacer()
                Button {
                    presented1.toggle()
                } label: {
                    Text("View All")
                        .foregroundColor(.gray)
                }
                .fullScreenCover(isPresented: $presented1, content: AllPopular.init)
                
                
            }
            .padding(.horizontal)
            .padding(.bottom)
            ScrollView(.horizontal,showsIndicators: false){
                
                
                HStack{
                    ForEach(model.Recipes, id: \.id) {item in
                        CardButton()
                            .padding(.horizontal)
                    }
                }
            }
        }
        .environmentObject(model)
        .onAppear {
            model.getData()
        }
    }
}


struct AllPopular:View{
    @Environment(\.presentationMode) var presentationMode
    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
    ]
    @EnvironmentObject var model: RecipesViewModel
    
    var body: some View{
        VStack{
            HStack{
                Button {
                    presentationMode.wrappedValue.dismiss()
                } label: {
                    Image(systemName: "arrow.left")
                        .foregroundColor(.black)
                        .font(.system(size: 30))
                }
                Spacer()
                Text("All Popular Recipes")
                    .font(.system(size: 20))
                    .fontWeight(.semibold)
                    .padding()
                Spacer()
            }
            .padding(.horizontal)
            ScrollView {
                LazyVGrid(columns: columns, spacing: 20) {
                    ForEach(model.Recipes, id: \.id) { item in
                        CardButton()
                    }
                }
                .padding(.horizontal)
            }
        }
        .navigationTitle("")
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct CardButton:View{
    @EnvironmentObject var model: RecipesViewModel
    @State var presented: Bool = false
    
    var body: some View{
        ForEach(model.Recipes, id: \.id){item in
//            NavigationLink(destination: Recipe(name: item.name, cuisine: item.cuisine, difficulty: item.difficulty, dishType: item.dishType, prepMins: item.prepMins, prepHours: item.prepHours, cookMins: item.cookMins, cookHours: item.cookHours, restMins: item.restMins, restHours: item.restHours, likes: item.likes, imageURL: item.imageURL), label: {CardView()}).navigationBarBackButtonHidden(true).navigationTitle("").navigationBarHidden(true)
            Button {
                presented.toggle()
            } label: {
                CardView()
            }
            .fullScreenCover(isPresented: $presented) {
                Recipe(name: item.name, cuisine: item.cuisine, difficulty: item.difficulty, dishType: item.dishType, prepMins: item.prepMins, prepHours: item.prepHours, cookMins: item.cookMins, cookHours: item.cookHours, restMins: item.restMins, restHours: item.restHours, likes: item.likes, imageURL: item.imageURL)
            }



        }
    }
}

这是我从 firestore 获取数据的地方

import Foundation
import Firebase

class RecipesViewModel: ObservableObject {
    
    @Published var Recipes = [RecipesData]()
    
    func getData() {
        
        // Get a reference to the database
        let db = Firestore.firestore()
        
        // Read the documents at a specific path
        db.collection("Recipes").getDocuments { snapshot, error in
            
            // Check for errors
            if error == nil {
                // No errors
                
                if let snapshot = snapshot {
                    
                    // Update the list property in the main thread
                    DispatchQueue.main.async {
                        
                        
                        self.Recipes = snapshot.documents.map { d in
                            
                            return RecipesData(id: d["id"] as? String ?? "", name: d["name"] as? String ?? "", cuisine: d["cuisine"] as? String ?? "", difficulty: d["difficulty"] as? String ?? "", dishType: d["dishType"] as? String ?? "", prepMins: d["prepMins"] as? Int ?? 0, prepHours: d["prephours"] as? Int ?? 0, cookMins: d["cookMins"] as? Int ?? 0, cookHours: d["cookHours"] as? Int ?? 0, restMins: d["restMins"] as? Int ?? 0, restHours: d["restHours"] as? Int ?? 0, likes: d["likes"] as? Int ?? 0, imageURL: d["imageURL"] as? String ?? "")
                        }
                    }
                    
                    
                }
            }
            else {
                
            }
        }
    }
}

这里是 RecipesData 的结构

import SwiftUI
import FirebaseFirestoreSwift

struct RecipesData: Identifiable{
    var id: String
    var name: String
    var cuisine: String
    var difficulty: String
    var dishType: String
    var prepMins: Int
    var prepHours: Int
    var cookMins: Int
    var cookHours: Int
    var restMins: Int
    var restHours: Int
    var likes: Int
    var imageURL: String
}

Here is an image of my output

我哪里做错了,我该如何解决?

编辑 - 控制台日志

您在每个子视图中迭代 collection 中的每个项目。

删除这个不必要的 ForEach 循环:

!注意! 我不得不模拟你的 FireBase 模型实现,所以我不知道是否有你的 getData() 有没有问题。

struct CardView: View {
// Remove the environment viewmodel as you probably won´t need it
//    @EnvironmentObject var model: RecipesViewModel
// You need the instance of one recipy here not all of them, because this
// view should represent one instance of RecipesData
    var recipesData: RecipesData
    var body: some View {
//        ForEach(model.Recipes,id: \.id) {item in // Remove this loop
            VStack{
                ZStack(alignment: .bottomTrailing){
                    WebImage(url: URL(string: recipesData.imageURL))
                        .resizable()
                        .scaledToFit()
                        .cornerRadius(8)
                        .frame(width: 150, height: 150)
                    HStack{
                        Text("\(recipesData.likes)")
                        
                        Image(systemName: "heart")
                    }
                    .foregroundColor(Color(hex: "FF0044"))
                    .padding(5)
                    .background(.white)
                    .cornerRadius(8)
                    .padding(7)
                    
                }
                
                
                Text(recipesData.name)
                    .foregroundColor(.black)
                    .fontWeight(.medium)
                    .font(.system(size: 20))
                    .padding(.top)
                
                
                HStack{
                    Image(systemName: "clock")
                        .foregroundColor(.gray)
                    Text("\(recipesData.prepMins) mins")
                        .foregroundColor(.gray)
                }
                .padding(5)
                .padding(.trailing)
            }
//        }
    }
}

struct PopularRecipes:View{
    // As far as I understood is this your Top View so it is ok to create the viewmodel here
    @State var presented1:Bool = false
    @StateObject var model = RecipesViewModel()
    
    var body: some View{
        
        VStack{
            HStack{
                Text("Popular")
                    .font(.system(size: 20))
                    .fontWeight(.medium)
                Spacer()
                Button {
                    presented1.toggle()
                } label: {
                    Text("View All")
                        .foregroundColor(.gray)
                }
                .fullScreenCover(isPresented: $presented1, content: AllPopular.init)
                
                
            }
            .padding(.horizontal)
            .padding(.bottom)
            ScrollView(.horizontal,showsIndicators: false){
                
                // Yes. ForEach here in order to present all recipesData
                HStack{
                    ForEach(model.Recipes, id: \.id) {item in
                        CardButton(recipesData: item)
                            .padding(.horizontal)
                    }
                }
            }
        }
        .environmentObject(model)
        .onAppear {
            model.getData()
        }
    }
}


struct AllPopular:View{
    @Environment(\.presentationMode) var presentationMode
    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
    ]
    @EnvironmentObject var model: RecipesViewModel
    
    var body: some View{
        VStack{
            HStack{
                Button {
                    presentationMode.wrappedValue.dismiss()
                } label: {
                    Image(systemName: "arrow.left")
                        .foregroundColor(.black)
                        .font(.system(size: 30))
                }
                Spacer()
                Text("All Popular Recipes")
                    .font(.system(size: 20))
                    .fontWeight(.semibold)
                    .padding()
                Spacer()
            }
            .padding(.horizontal)
            ScrollView {
                LazyVGrid(columns: columns, spacing: 20) {
                    ForEach(model.Recipes, id: \.id) { item in
                        CardButton(recipesData: item)
                    }
                }
                .padding(.horizontal)
            }
        }
        .navigationTitle("")
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct CardButton:View{
// You don´t need the viewmodel here
//    @EnvironmentObject var model: RecipesViewModel
    @State var presented: Bool = false
// Again a single instance of RecipesData is all you need here
    var recipesData: RecipesData
    var body: some View{
// Remove the loop
//        ForEach(model.Recipes, id: \.id){item in
//            NavigationLink(destination: Recipe(name: item.name, cuisine: item.cuisine, difficulty: item.difficulty, dishType: item.dishType, prepMins: item.prepMins, prepHours: item.prepHours, cookMins: item.cookMins, cookHours: item.cookHours, restMins: item.restMins, restHours: item.restHours, likes: item.likes, imageURL: item.imageURL), label: {CardView()}).navigationBarBackButtonHidden(true).navigationTitle("").navigationBarHidden(true)
            Button {
                presented.toggle()
            } label: {
                //Pass the single RecipesData to your CardView
                CardView(recipesData: recipesData)
            }
            .fullScreenCover(isPresented: $presented) {
                Recipe(name: recipesData.name, cuisine: recipesData.cuisine, difficulty: recipesData.difficulty, dishType: recipesData.dishType, prepMins: recipesData.prepMins, prepHours: recipesData.prepHours, cookMins: recipesData.cookMins, cookHours: recipesData.cookHours, restMins: recipesData.restMins, restHours: recipesData.restHours, likes: recipesData.likes, imageURL: recipesData.imageURL)
            }



//        }
    }
}

我试图评论我所做的每一个决定。如果您还有疑问,请不要犹豫。但首先请阅读并尝试理解我做了哪些更改以及为什么。这几乎是一个简单的默认实现。你犯的错误很简单。


编辑:

请在您的视图模型中更改此设置并将控制台输出添加到您的问题:

if let snapshot = snapshot {
        
        // Update the list property in the main thread
        DispatchQueue.main.async {
            
            
            self.Recipes = snapshot.documents.map { d in
                
                return RecipesData(id: d["id"] as? String ?? "", name: d["name"] as? String ?? "", cuisine: d["cuisine"] as? String ?? "", difficulty: d["difficulty"] as? String ?? "", dishType: d["dishType"] as? String ?? "", prepMins: d["prepMins"] as? Int ?? 0, prepHours: d["prephours"] as? Int ?? 0, cookMins: d["cookMins"] as? Int ?? 0, cookHours: d["cookHours"] as? Int ?? 0, restMins: d["restMins"] as? Int ?? 0, restHours: d["restHours"] as? Int ?? 0, likes: d["likes"] as? Int ?? 0, imageURL: d["imageURL"] as? String ?? "")
            }

            print(Recipes) // add this
        }
        
        
    }

编辑 2:

控制台中的错误消息表示您的 ID 不唯一。但他们必须如此。您可能在创建和上传它们时没有设置它们。您需要通过

为他们设置一个唯一的 ID
  • 正在加载添加 id 的食谱并再次上传它们
  • 通过 Firestore 控制台在线编辑食谱
  • 删除它们并上传新的,其中 id 设置为唯一值

要获得唯一值,您可以这样做:

id = UUID().uuidString