You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI ViewModel中等待Delegate回调后执行后续代码方案

解决SwiftUI中Stripe 3D认证代理回调后的异步等待问题

核心场景与问题

在SwiftUI项目中,通过UIViewControllerRepresentable集成Stripe的STPPaymentHandler完成3D认证流程,代理逻辑封装在UIViewControllerRepresentable的Coordinator中。当前执行present3dView.toggle()展示认证WebView后,ViewModel需要等待代理回调更新is3dSuccess状态,才能执行后续的订阅确认逻辑。直接同步判断状态会因代理未回调而失效,使用while循环等待则会阻塞主线程,导致WebView无法正常加载。

解决方案

通过异步等待机制监听ViewModel状态变化,在不阻塞UI的前提下等待代理回调完成,再执行后续业务逻辑。具体步骤如下:

1. 改造ViewModel,添加异步等待状态的方法

在ViewModel中新增一个异步方法,利用withCheckedContinuation结合Combine监听is3dSuccess的状态变化,等待认证结果返回:

import Combine

class PaymentViewModel: ObservableObject {
    // 原有属性保持不变
    @Published var is3dSuccess: String = ""
    @Published var present3dView: Bool = false
    // ...其他属性
    
    // 新增:异步等待3D认证结果
    private func waitFor3DAuthResult() async -> String {
        await withCheckedContinuation { continuation in
            // 订阅is3dSuccess的变化,一旦有非空结果就恢复continuation
            let cancellable = $is3dSuccess.sink { result in
                if !result.isEmpty {
                    continuation.resume(returning: result)
                    cancellable.cancel() // 完成后取消订阅
                }
            }
        }
    }
}

2. 调整3D认证触发后的业务逻辑

在present3dView.toggle()之后,调用异步等待方法,待代理回调更新状态后再执行订阅确认:

// 原有代码中触发3D认证的分支
if request.next_action != nil {
    self.clientSecret = request.client_secret
    if self.clientSecret != nil {
        self.present3dView.toggle()
        
        // 异步等待3D认证结果,替代原有的同步判断
        let authResult = await waitFor3DAuthResult()
        
        if authResult == "success" {
            // 执行订阅确认逻辑
            let stripeID = retrieveIntent(self.clientSecret!)
            let isSuccessSubscription = try await self.confirmSubscription(stripeId: stripeID, card: paymentCard)
            
            if isSuccessSubscription {
                self.subscriptionState = .success
                self.successMessage = "Subscription Successful"
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){
                    self.loadingState = .loaded
                    self.subscriptionState = .idle
                }
            }
        } else {
            DispatchQueue.main.async {
                self.subscriptionState = .failed
                self.errorMessage = "Payment Failed"
            }
        }
    }
}

3. 优化PaymentWebView的状态监听

确保WebView在代理回调完成后正确关闭,同时保证状态传递的准确性:

struct PaymentWebView: View {
    @EnvironmentObject var viewModel: PaymentViewModel
    let clientSecret: String
    @State var isSuccess: Bool = false

    var body: some View {
        PaymentHandlerView(clientSecret: clientSecret, isSuccess: $isSuccess, successMessage: $viewModel.is3dSuccess)
            .onChange(of: viewModel.is3dSuccess) { newValue in
                // 只要is3dSuccess有结果就关闭WebView
                if !newValue.isEmpty {
                    viewModel.present3dView = false
                }
            }
    }
}

4. 确认代理回调的状态更新

检查Coordinator的回调逻辑,确保正确将认证结果同步到ViewModel:

class Coordinator: NSObject, STPAuthenticationContext, PaymentHandlerDelegate {
    var parent: PaymentHandlerView
    
    init(_ parent: PaymentHandlerView) {
        self.parent = parent
        super.init()
        parent.VC.delegate = self
    }
    
    func didFinishPaymentAuthentication(_ success: Bool, value: String) {
        parent.isSuccess.wrappedValue = success
        parent.successMessage.wrappedValue = value // 确保此绑定正确指向viewModel.is3dSuccess
    }
    
    func authenticationPresentingViewController() -> UIViewController {
        return parent.VC
    }        
}

关键说明

  • 原同步判断逻辑的问题:present3dView.toggle()后立即判断is3dSuccess,此时代理尚未回调,状态仍为初始值,导致逻辑无法正确触发。
  • 异步等待的优势:通过withCheckedContinuation和Combine订阅,既不会阻塞主线程,又能在状态变化时及时响应,保证WebView正常加载和后续逻辑的执行顺序。

内容的提问来源于stack exchange,提问作者Umair Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 02:24:52