SwiftUI - ViewModel state not updating after async network call
16:57 19 Dec 2025

I'm working on a payment review screen, and I need to make a network call to get some legal agreement content. The problem is, having this call as a task attached to the View makes the View unresponsive to state changes, and I'm seeing a lot of "=== AttributeGraph: cycle detected through attribute xxxxx ===" console warnings. Here's a simplified version of the view:

struct OneTimePayReviewExperienceView: View {
    @State var authorized: Bool = false
    @StateObject var paymentExpViewModel = PaymentExpViewModel()
        
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 24) {

                
                if let agreementText = paymentExpViewModel.agreementHtml {
                    Text(html: agreementText)
                }
                
                HStack(spacing: 8) {
                    CheckboxTwoToneView(selected: authorized)
                    Text("I authorize this payment to be made.")
                    Spacer()
                }
                .onTapGesture {
                    self.authorized = true
                }
                
                
            }//VStack
            .task {
                await paymentExpViewModel.fetchPartnerPaymentAgreement(
                    agreementType: .onetimepayment,
                    policySourceSystemName: paymentInput.policySourceSystem,
                    partnerId: paymentInput.policyPartnerId
                )
            }
        }//ScrollView
    }//body
}

And here's a simplified version of the ViewModel:

@MainActor
class PaymentExpViewModel: ObservableObject {
    @Published var agreementHtml: String?
    let dataManager = AFIDataManager.sharedManager

    func fetchPartnerPaymentAgreement(agreementType: AgreementType,policySourceSystemName: String,partnerId: String) async {
        do {
            let agreementRequest = APIRouter().createRequest(.agreements(agreementType, policySourceSystemName))
            let richContent = try await billingService.fetch(type: AgreementResponse.self, request: agreementRequest)
            var html = richContent.richContent
            
            let partnerPhoneNumber = dataManager.partnerPhoneNumbeer(for: partnerId)
            html += partnerPhoneNumber
            
            self.agreementHtml = html
        } catch {
            self.agreementHtml = nil
        }
    }
}

The problem is that the "authorized" state var isn't being set (or the state changes aren't being picked up?) when I have the code to call the network. (If I comment that code out, the state updates fine). I'm guessing the AttributeGraph warnings mean the view updates are getting stuck in a loop somehow, but I'm not sure why. Is it a threading issue with the async call? I tried adding @MainActor to the ViewModel to force it to execute on the main thread, but it didn't seem to work. Is it the way I'm updating the agreementText?

Is there a good way to debug this so I can understand what's going on?

swiftui async-await