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
相关产品推荐
相关产品推荐

