视图未使用 SwiftUI 中 Firestore 的数据更新
The view is not updated with data from Firestore in SwiftUI
我有一个小问题,我正在从 Firebase 和 Firestore 数据库中获取数据,但只有在我选择其中一个类别时才会更新视图。该视图有 4 个类别,可以包含项目。我会 post 在这个问题的末尾放一个简短的视频来说明我的问题是什么。
所以,这是我的 ViewModel :
class HomeViewModell : ObservableObject {
@Published var productType : ProductType = .Wearable
@Published var products = [Product]()
@Published var filteredProducts : [Product] = []
var searchCancellable: AnyCancellable?
init() {
getData()
filteredProductByType()
}
func getData() {
FirebaseManager.shared.firestore.collection("products").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
DispatchQueue.main.async {
self.products = documents.map { d in
return Product(id: d.documentID, type: ProductType(rawValue: d["type"] as? String ?? "") ?? .Laptops
, title: d["title"] as? String ?? "",
subtitle: d["subtitle"] as? String ?? "",
price: d["price"] as? String ?? "", productImage: d["productImage"] as? String ?? ""
)
}
}
}
}
func filteredProductByType() {
DispatchQueue.global(qos: .userInteractive).async {
let results = self.products
//Since it will require more memory so were using lazy to perform more
.lazy
.filter { product in
return product.type == self.productType
}
// Limiting results..
.prefix(4)
DispatchQueue.main.async {
self.filteredProducts = results.compactMap({ product in
return product
})
}
}
}
}
这些是我的结构:
struct Product : Identifiable, Hashable {
var id = UUID().uuidString
var type : ProductType
var title : String
var subtitle : String
var description : String = ""
var price : String
var productImage : String = ""
var quantity : Int = 1
}
enum ProductType : String, CaseIterable {
case Wearable = "Wearable"
case Laptops = "Laptops"
case Phones = "Phones"
case Tablets = "Tablets"
}
这是我的观点
struct Homee: View {
@Namespace var animation : Namespace.ID
@EnvironmentObject var sharedData : SharedDataViewModel
@ObservedObject var homeData : HomeViewModel = HomeViewModel()
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing : 15) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing : 18) {
ForEach(ProductType.allCases, id: \.self) { type in
productTypeView(type: type)
}
}
.padding(.horizontal, 25)
}
.padding(.top, 28 )
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing : 25) {
ForEach(homeData.filteredProducts) { product in
ProductCardView(product: product)
}
}
.padding(.horizontal, 25)
.padding(.bottom)
.padding(.top, 80)
}
.padding(.top, 30)
}
.padding(.vertical)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("HomeBG"))
.onChange(of: homeData.productType) { newValue in
homeData.filteredProductByType()
}
}
@ViewBuilder
func productTypeView(type : ProductType) -> some View {
Button {
withAnimation {
homeData.productType = type
}
} label: {
Text(type.rawValue)
.font(.custom(customFont, size: 15))
.fontWeight(.semibold)
.padding(.bottom, 10)
.overlay(
ZStack {
if homeData.productType == type {
Capsule()
.fill(Color("Purple"))
.matchedGeometryEffect(id: "PRODUCTTAB", in: animation)
.frame(height : 2)
} else {
Capsule()
.fill(Color.clear)
.frame(height : 2)
}
}
.padding(.horizontal, -5)
, alignment: .bottom
)
}
}
@ViewBuilder
func ProductCardView(product : Product) -> some View {
VStack(spacing: 10) {
//Addid matched geometry effect
ZStack {
if sharedData.showDetailProduct {
Image(product.productImage)
.resizable()
.aspectRatio(contentMode: .fit)
}
else {
Image(product.productImage)
.resizable()
.aspectRatio(contentMode: .fit)
.matchedGeometryEffect(id: "\(product.id)IMAGE", in: animation)
}
}
.frame(width: getRect().width / 2.5, height: getRect().width / 2.5)
.offset(y: -80)
.padding(.bottom, -80
Text(product.title)
.font(.custom(customFont, size: 18))
.fontWeight(.semibold)
.padding(.top)
Text(product.subtitle)
.font(.custom(customFont, size: 14))
.fontWeight(.semibold)
.foregroundColor(.gray)
Text(product.price)
.font(.custom(customFont, size: 16))
.fontWeight(.bold)
.foregroundColor(Color("Purple"))
.padding(.top, 5)
}
.padding(.horizontal,20)
.padding(.bottom, 22)
.background(
Color.white.cornerRadius(25)
)
}
}
有人知道问题出在哪里吗?
因此,只有在选择类别时,才会显示我的项目。
Watch here the issue
getData()
中的操作是异步的。这意味着该函数内部发生的事情不一定会在您在 init
方法中调用 filteredProductByType()
时执行。因此,在第一个 运行 上,它几乎可以保证 filteredProducts
为空。
要解决此问题,例如,您可以在 getData
.
中的 self.products = documents.map {
闭包之后调用 filteredProductByType
话虽如此,处理问题的更有效方法可能是将 filteredProducts
转换为 计算的 属性 以生成其结果被调用时。类似于:
var filteredProducts: [Product] {
products.filter { [=10=].type == self.productType }
}
然后,您再也不必调用 filteredProductByType
并担心它发生的顺序。
我有一个小问题,我正在从 Firebase 和 Firestore 数据库中获取数据,但只有在我选择其中一个类别时才会更新视图。该视图有 4 个类别,可以包含项目。我会 post 在这个问题的末尾放一个简短的视频来说明我的问题是什么。
所以,这是我的 ViewModel :
class HomeViewModell : ObservableObject {
@Published var productType : ProductType = .Wearable
@Published var products = [Product]()
@Published var filteredProducts : [Product] = []
var searchCancellable: AnyCancellable?
init() {
getData()
filteredProductByType()
}
func getData() {
FirebaseManager.shared.firestore.collection("products").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
DispatchQueue.main.async {
self.products = documents.map { d in
return Product(id: d.documentID, type: ProductType(rawValue: d["type"] as? String ?? "") ?? .Laptops
, title: d["title"] as? String ?? "",
subtitle: d["subtitle"] as? String ?? "",
price: d["price"] as? String ?? "", productImage: d["productImage"] as? String ?? ""
)
}
}
}
}
func filteredProductByType() {
DispatchQueue.global(qos: .userInteractive).async {
let results = self.products
//Since it will require more memory so were using lazy to perform more
.lazy
.filter { product in
return product.type == self.productType
}
// Limiting results..
.prefix(4)
DispatchQueue.main.async {
self.filteredProducts = results.compactMap({ product in
return product
})
}
}
}
}
这些是我的结构:
struct Product : Identifiable, Hashable {
var id = UUID().uuidString
var type : ProductType
var title : String
var subtitle : String
var description : String = ""
var price : String
var productImage : String = ""
var quantity : Int = 1
}
enum ProductType : String, CaseIterable {
case Wearable = "Wearable"
case Laptops = "Laptops"
case Phones = "Phones"
case Tablets = "Tablets"
}
这是我的观点
struct Homee: View {
@Namespace var animation : Namespace.ID
@EnvironmentObject var sharedData : SharedDataViewModel
@ObservedObject var homeData : HomeViewModel = HomeViewModel()
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing : 15) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing : 18) {
ForEach(ProductType.allCases, id: \.self) { type in
productTypeView(type: type)
}
}
.padding(.horizontal, 25)
}
.padding(.top, 28 )
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing : 25) {
ForEach(homeData.filteredProducts) { product in
ProductCardView(product: product)
}
}
.padding(.horizontal, 25)
.padding(.bottom)
.padding(.top, 80)
}
.padding(.top, 30)
}
.padding(.vertical)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("HomeBG"))
.onChange(of: homeData.productType) { newValue in
homeData.filteredProductByType()
}
}
@ViewBuilder
func productTypeView(type : ProductType) -> some View {
Button {
withAnimation {
homeData.productType = type
}
} label: {
Text(type.rawValue)
.font(.custom(customFont, size: 15))
.fontWeight(.semibold)
.padding(.bottom, 10)
.overlay(
ZStack {
if homeData.productType == type {
Capsule()
.fill(Color("Purple"))
.matchedGeometryEffect(id: "PRODUCTTAB", in: animation)
.frame(height : 2)
} else {
Capsule()
.fill(Color.clear)
.frame(height : 2)
}
}
.padding(.horizontal, -5)
, alignment: .bottom
)
}
}
@ViewBuilder
func ProductCardView(product : Product) -> some View {
VStack(spacing: 10) {
//Addid matched geometry effect
ZStack {
if sharedData.showDetailProduct {
Image(product.productImage)
.resizable()
.aspectRatio(contentMode: .fit)
}
else {
Image(product.productImage)
.resizable()
.aspectRatio(contentMode: .fit)
.matchedGeometryEffect(id: "\(product.id)IMAGE", in: animation)
}
}
.frame(width: getRect().width / 2.5, height: getRect().width / 2.5)
.offset(y: -80)
.padding(.bottom, -80
Text(product.title)
.font(.custom(customFont, size: 18))
.fontWeight(.semibold)
.padding(.top)
Text(product.subtitle)
.font(.custom(customFont, size: 14))
.fontWeight(.semibold)
.foregroundColor(.gray)
Text(product.price)
.font(.custom(customFont, size: 16))
.fontWeight(.bold)
.foregroundColor(Color("Purple"))
.padding(.top, 5)
}
.padding(.horizontal,20)
.padding(.bottom, 22)
.background(
Color.white.cornerRadius(25)
)
}
}
有人知道问题出在哪里吗?
因此,只有在选择类别时,才会显示我的项目。
Watch here the issue
getData()
中的操作是异步的。这意味着该函数内部发生的事情不一定会在您在 init
方法中调用 filteredProductByType()
时执行。因此,在第一个 运行 上,它几乎可以保证 filteredProducts
为空。
要解决此问题,例如,您可以在 getData
.
self.products = documents.map {
闭包之后调用 filteredProductByType
话虽如此,处理问题的更有效方法可能是将 filteredProducts
转换为 计算的 属性 以生成其结果被调用时。类似于:
var filteredProducts: [Product] {
products.filter { [=10=].type == self.productType }
}
然后,您再也不必调用 filteredProductByType
并担心它发生的顺序。