swiftuiobservablestateproperty-wrapper

SwiftUI: Observable Object does not update in View?


I am struggling here for days now: I have a async function that get's called onRecieve from a timer in a LoadingView. It calls the getData function from the class ViewModel. Data gets fetched with an HTTP Get Request and compared: if the fetched ID = to the transactionID in my app and the fetched Status = "Success", then the payment is successful.

This is toggled in my observable class. Have a look:

//View Model
@MainActor class ViewModel: ObservableObject {


@Published var fetchedData = FetchedData()
@Published var successfullPayment: Bool = false
@Published var information: String = "Versuch's weiter!"


// Function to fetch Data from the Databank
func getData() {
    
    guard let url = URL(string: getUrl) else {return}
    
    URLSession.shared.dataTask(with: url) { (data, res, err) in
        do{
            if let data = data {
                
                let result = try JSONDecoder().decode(FetchedData.self, from: data)
                
                DispatchQueue.main.async {
                    self.fetchedData = result
                    
                    if self.fetchedData.id == transactionId && self.fetchedData.statuscode == "Success" {
                        
                        self.successfullPayment = true
                                                                                
                        print("Payment was successful")
                        
                    } else {print("Pending ...")}

                }
            } else {
                print("No data")
            }
            
        } catch (let error) {
            print(error.localizedDescription)
        }
    }.resume()
}
}

And this is my observing LoadingView:

struct LoadingView: View {
            
    //Timer
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var counter = 0
    
    @State var paymentCancelled = false
        
    @ObservedObject var observable: ViewModel
            
    var body: some View {
        
            ZStack {
                
                Image("money")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                
                VStack {
                    
                    if self.observable.successfullPayment == true{
                        Text("Thanks you" as String)
                            .font(.largeTitle)
                            .fontWeight(.black)
                            .multilineTextAlignment(.center)
                        .padding(.top, 100)
                    } else {
                        Text("Paying ..." as String)
                            .font(.largeTitle)
                            .fontWeight(.black)
                            .multilineTextAlignment(.center)
                        .padding(.top, 100)
                    }
                    
                    PushView(destination: CancelledView(), isActive: $paymentCancelled) {
                        Spacer()
                    }
                    
                    
                    Button {
                        
                        paymentCancelled.toggle()
                        print("payment cancelled!")
                        
                    } label: {
                        Label("Abbrechen", systemImage: "nosign")
                            .padding(.horizontal, 40)
                            .padding(.vertical, 10.0)
                            .background(Color.blue)
                            .foregroundColor(Color.white)
                            .cornerRadius(10)
                            .font(Font.body.weight(.medium))
                    }
                    .padding(.bottom, 50)
                    
                }
                .navigationBarTitle("")
                .navigationBarHidden(true)
            }
            .onReceive(timer) { time in
                if counter == 90 {
                    timer.upstream.connect().cancel()
                    print("Timer cancelled")
                } else {
                    ViewModel().getData()
                    
                }
                
                counter += 1
            }
        
    }
    }

But the published var successfullPayment doesn't update the View. What am I missing here? Has it to do with the async function?


Solution

  • I will focus only on the call to getData(). Your view is calling the following command:

    ViewModel().getData()
    

    This means that you are calling the function on a new instance of the view model. So, the variables fetchedData and successfullPayment will be updated on an instance which is not the one being used in the view.

    The first step would be to use the same instance that you have in your view:

    observable.getData()
    

    Be sure that the view calling LoadingView has a @StateObject of type ViewModel and that you are passing it correctly.