This is my first SwiftUI app. I have done a lot of searches and could not find a solution to this.
I want to display Products from different categories. For that, I have top bar horizontally scrollable menu. When selecting any category, I want to display each category's products. When on loading first category products are displaying correctly, but selecting any other category, products are not updating accordingly. However, When I navigate to any other view and come back to this view, products are displaying correctly.
What I tried
I tried different views to display products like List/ScrollView. No luck.
Here is the complete code of my View
struct ProductListView: View {
@State var data:[Product]
@State private var categoryItems:[Product] = []
@State private var uniqueCategories:[Product] = []
@State private var numberOfRows:Int = 0
@State private var selectedItem:Int = 0
@State private var image:UIImage?
@EnvironmentObject var authState: AuthenticationState
@ViewBuilder
var body: some View {
NavigationView{
VStack{
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<self.data.removingDuplicates(byKey: { $0.category }).count) { i in
Text(self.data.removingDuplicates(byKey: { $0.category })[i].category)
.underline(self.selectedItem == i ? true:false, color: Color.orange)
.foregroundColor(self.selectedItem == i ? Color.red:Color.black)
//.border(Color.gray, width: 2.0)
.overlay(
RoundedRectangle(cornerRadius: 0.0)
.stroke(Color.init(red: 236.0/255.0, green: 240.0/255.0, blue: 241.0/255.0), lineWidth: 1).shadow(radius: 5.0)
).padding(.horizontal)
//.shadow(radius: 5.0)
.onTapGesture {
self.selectedItem = i
_ = self.getSelectedCategoryProducts()
print("Category Items New Count: \(self.categoryItems.count)")
}
Spacer()
Divider().background(Color.orange)
}
}
}
.frame(height: 20)
Text("My Products").foregroundColor(Color.red).padding()
Spacer()
if(self.data.count == 0){
Text("You didn't add any product yet.")
}
if (self.categoryItems.count>0){
ScrollView(.vertical){
ForEach(0..<Int(ceil(Double(self.categoryItems.count)/2.0)), id: \.self){ itemIndex in
return HStack() {
NavigationLink(destination:ProductDetailView(index: self.computeIndexes(currentIndex: itemIndex,cellNo: 1), data: self.categoryItems, image: UIImage())){
ProductTile(index: self.computeIndexes(currentIndex: itemIndex, cellNo: 1), data: self.categoryItems, image: UIImage())
}
NavigationLink(destination:ProductDetailView(index: self.computeIndexes(currentIndex: itemIndex,cellNo: 2), data: self.categoryItems, image: UIImage())){
ProductTile(index: self.computeIndexes(currentIndex: itemIndex, cellNo: 2), data: self.categoryItems, image: UIImage())
}
}.padding(.horizontal)
}
}.overlay(
RoundedRectangle(cornerRadius: 10.0)
.stroke(Color.init(red: 236.0/255.0, green: 240.0/255.0, blue: 241.0/255.0), lineWidth: 1).shadow(radius: 5.0)
)
}
}
}
.onAppear(perform: {
_ = self.getSelectedCategoryProducts()
//print("Loading updated products....")
if (self.authState.loggedInUser != nil){
FireStoreManager().loadProducts(userId: self.authState.loggedInUser!.uid) { (isSuccess, data) in
self.data = data
}
}
})
.padding()
}
func populateUniqueCategories() -> [Product] {
let uniqueRecords = self.data.reduce([], {
$0.contains($1) ? $0 : $0 + [$1]
})
print("Unique Items: \(uniqueRecords)")
return uniqueRecords
}
func getSelectedCategoryProducts() -> [Product] {
var categoryProducts:[Product] = []
self.data.forEach { (myProduct) in
if(myProduct.category == self.populateUniqueCategories()[selectedItem].category){
categoryProducts.append(myProduct)
}
}
self.categoryItems.removeAll()
self.categoryItems = categoryProducts
return categoryProducts
}
func computeIndexes(currentIndex:Int, cellNo:Int) -> Int {
var resultedIndex:Int = currentIndex
if (cellNo == 1){
resultedIndex = resultedIndex+currentIndex
print("Cell 1 Index: \(resultedIndex)")
}else{
resultedIndex = resultedIndex + currentIndex + 1
print("Cell 2 Index: \(resultedIndex)")
}
return resultedIndex
}
}
And this is the array extension
extension Array {
func removingDuplicates<T: Hashable>(byKey key: (Element) -> T) -> [Element] {
var result = [Element]()
var seen = Set<T>()
for value in self {
if seen.insert(key(value)).inserted {
result.append(value)
}
}
return result
}
}
I would really thankful if you help me to sort out this issue. Best Regards
The reason is in using static range ForEach constructor, which does not expect any changes, by design, so not updated.
So instead of using like
ForEach(0..<self.data.removingDuplicates(byKey: { $0.category }).count) { i in
^^^^^^ range !!
and
ForEach(0..<Int(ceil(Double(self.categoryItems.count)/2.0)), id: \.self){
^^^^ range
it needs to use direct data container, like
ForEach(self.data.removingDuplicates(byKey: { $0.category }), id: \.your_product_id) { product in
^^^^^^^^^ array
Note: if you need index during iteration, it is possible by using .enumerated()
to container, eg: in this post