1

I'm working on a calorie-tracker app. In my App, I can open the Detail side of some products, set the amount and add the product to the "Cart". Later, I want to read out all collected datas from the array and show them an a short overview.

But this View won't be updated after making changer on the array. Due to I storage the datas with the userDefaults, I always have to reopen the app to update the view. Only then, the hole array will be displayed.

My Class Cart:

import Foundation

struct Order: Equatable, Identifiable, Codable {
    var id = UUID()
    var product: Product
    var modifier: Double
    var date: Date
}


class Cart: ObservableObject {
    
    @Published var orders = [Order]()
    
    static let saveKey = "SavedData"
    
    init() {
        
        if let data = UserDefaults.standard.data(forKey: Self.saveKey) {
            if let decoded = try? JSONDecoder().decode([Order].self, from: data) {
                self.orders = decoded
            }
        } else {
            self.orders = []
        }
    }
    
    // save order
    func save() {
        if let encoded = try? JSONEncoder().encode(self.orders) {
            UserDefaults.standard.set(encoded, forKey: Self.saveKey)
        }
    }
    
    // add to order
    func add(order: Order) {
        self.orders.append(order)
        print("product added to cart")
        save()
    }
    
    // remove from order
    func remove(order: Order) {
        if let index = orders.firstIndex(of: order) {
            orders.remove(at: index)
        }
    }
    
}

I made a View to apply the amount of any special product.

import SwiftUI

struct AmountView: View {
    
    @EnvironmentObject var cart: Cart
    
    @State private var textInput = ""
    @State private var orderFinished = false
    var product: Product
        
    func StringDoubleConverter(text: String) -> String {
            return String(format: "%.2f", Double(textInput.replacingOccurrences(of: ",", with: ".")) ?? 0)
        }
    
    var body: some View {
        VStack {
            Form {
                Section(header: Text("Mengenangabe")) {
                    
                    // input for the amount
                    AmountInputView(textInput: $textInput)
                    if !orderFinished {
                        Button("Hinzufügen", action: {
                            orderFinished = true
                            
                            hideKeyboard()
        
                            // add product to the cart
                            self.cart.add(order: Order(product: product, modifier: Double(StringDoubleConverter(text: textInput))!, date: Date()))

                            
                        })
                        .disabled(textInput == "")
                        .animation(.default)
                    } else {
                        Text("Wurde zum Logbuch hinzugefügt")
                            .foregroundColor(.blue)
                    }
                }
                productNutritionCollectionView(product: product, modifier: Double(StringDoubleConverter(text: textInput))!)

            }
        }
    }
}

struct AmountView_Previews: PreviewProvider {
    
    static var previews: some View {
        AmountView(product: Product.exampleProduct).environmentObject(Cart())
    }
}

Then, I want to display all products in the order in a logbook view using a Form and a ForEach lope.

struct LogbookView: View {
    
    func deleteProducts(at offsets: IndexSet) {
        cart.orders.remove(atOffsets: offsets)
        cart.save()
    }
    
    @EnvironmentObject var cart: Cart
    
    @State private var date = Date()
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("List")) {
                    ForEach(cart.orders) { order in
                        Text(order.product.name)
                    }
                    .onDelete(perform: { indexSet in
                        deleteProducts(at: indexSet)
                    })
                    
                }
            }
            .navigationBarTitle(Text("Logbuch"), displayMode: .automatic)
            .navigationBarItems(trailing: DateView(date: $date))
        }
    }
}

struct LogbookView_Previews: PreviewProvider {
    
    
    static var previews: some View {
        LogbookView().environmentObject(Cart())
    }
}

I'm using a AppTab View to navigate the app. Therefore, I changed the AppTab View in the main Struct to the default View with an environment object of Cart.

@main
struct KalorientrackerApp: App {
    
    var body: some Scene {
        
        WindowGroup {
            AppTabView().environmentObject(Cart())
        }
    }
}

struct KalorientrackerApp_Previews: PreviewProvider {
    static var previews: some View {
        Text("Hello, World!")
    }
}

I'm opening my AmountView using a .sheet

struct ProductDetailView: View {
    
    @State private var showAmountView = false
    let product: Product
    
    var body: some View {
        VStack {
            // placeholder Image
            Image(product.fullImage)
                .clipShape(Circle())
                .padding(.top, 5)
            Spacer()
            
            Form {
                productNutritionCollectionView(product: product, modifier: 100)
            }
        }
        
        // Titel for Navigation bar
        .navigationBarTitle(Text(product.name), displayMode: .inline)
        
        // Button to go to amount view
        .navigationBarItems(trailing: Button(action: {
            self.showAmountView = true
        }, label: {
            Image(systemName: "plus.circle")
                .padding(.leading, 20)
        }).sheet(isPresented: $showAmountView, content: {
            
            AmountView(product: product).environmentObject(Cart())
        }))
    }
}

struct ProductDetailView_Previews: PreviewProvider {
    static var previews: some View {
        ProductDetailView(product: Product.exampleProduct)    }
}

I already found some other discussions, but they didn't worked for me.

I'm using Xcode 12 beta 6 and iOS14 beta 6

Patrick
  • 273
  • 2
  • 6
  • Did you add in your SceneDelegate your environment var? window.rootViewController = UIHostingController(rootView: contentView.environmentObject(Cart())) Then init your Cart class loading UserDefaults – dig Aug 30 '20 at 19:26
  • SceneDelegate is no longer available in Xcode12. It's a new project made in the beta of Xcode. I added the cart to the @main struct. – Patrick Aug 30 '20 at 19:49
  • As far as I see you did not provide problem part of code. Would you show the parts where you create Cart, how you list orders, how you create sheet... or should we guess? – Asperi Aug 31 '20 at 04:16
  • Sorry for that. Added the missing code to the question. – Patrick Aug 31 '20 at 07:10
  • I had the a similar issue. This post solved it for me: https://stackoverflow.com/questions/57615920/published-property-wrapper-not-working-on-subclass-of-observableobject – elprl Nov 10 '21 at 21:48

1 Answers1

0

I found the bug myself. The problem was, that I committed explicit an .environmentObject in my .sheet action.

AmountView(product: product).environmentObject(Cart())

I removed .environmentObject(Cart()) from the .sheet action. Now it's working.

Thinking this caused the bug because I'm using the .environmentObject(Cart()) operator in the main View of my project.

Patrick
  • 273
  • 2
  • 6