I have a little issue, I'm fetching data from Firebase, from Firestore database, but the view is only updated when I'm selecting one of the category. The view has 4 categories which can contains items. I'll post a short video at the end of this questions to make it clear what's my problem.
So, this is my 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
})
}
}
}
}
These are my structs :
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"
}
And this is my view
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)
)
}
}
Anyone know what can be the issue ?
So, only when a category is selected, my items appears.
CodePudding user response:
The actions inside getData()
are asynchronous. That means that what happens inside that function will not necessarily have executed by the time you call filteredProductByType()
in your init
method. So, on first run, it pretty much guarantees that filteredProducts
will be empty.
To fix this, you could, for example, call filteredProductByType
after your self.products = documents.map {
closure inside getData
.
That being said, a more efficient way of dealing with the problems would likely be to turn filteredProducts
into a computed property that generates its results when called. Something like:
var filteredProducts: [Product] {
products.filter { $0.type == self.productType }
}
Then, you'd never have to call filteredProductByType
and worry about the order that it happens.