仅限 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
当我调用 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