SwiftUI带倒计时的Alert:如何动态更新Alert中使用的变量
SwiftUI Alert倒计时不更新问题修复
问题
在RedemptionView视图中,期望Alert能跟随countdown变量动态更新(该变量同时控制Proceed按钮的激活状态和Alert文本内容),但当前实现中countdown始终停留在初始值5,无法实时更新。
原因
- 线程问题:
Timer.scheduledTimer默认在后台线程执行,而SwiftUI的@State变量必须在主线程更新才能触发视图刷新,后台线程修改不会让Alert重新渲染。 - 未重置倒计时:每次弹出Alert时没有重置
countdown为初始值,可能导致复用旧值。 - Timer生命周期未管理:多次触发Alert会创建多个Timer,且未在Alert关闭时清理,可能导致逻辑混乱。
修复方案
修改点:
- 用
DispatchQueue.main.async在主线程更新countdown - 弹出Alert前重置
countdown为5 - 添加
@State存储Timer实例,在Alert关闭时销毁Timer
完整修复代码:
struct RedemptionView: View { @Environment(\.dismiss) var dismiss @State private var showQRCodeScanner = false @State private var showPasscodeView = false @State private var billAmount: String = "" @State private var isShowingRedemptionStepView = false @State private var showAlert = false @State private var countdown: Int = 5 // 添加Timer实例存储,用于管理生命周期 @State private var countdownTimer: Timer? var allowsDelitePointCollection: Bool var voucherTitle: String var discountId: Int var body: some View { VStack { DismissButton() VStack(spacing: 16) { Text("Scan the QR code provided by the staff, or pass your phone to them to enter the staff code.") .font(.subheadline) .multilineTextAlignment(.center) .padding(.horizontal) .fixedSize(horizontal: false, vertical: true) Button(action: { if !billAmount.isEmpty { // 弹出Alert前重置倒计时 countdown = 5 showAlert = true startCountdown() } else { showQRCodeScanner = true } }) { Text("Scan QR Code") .font(.headline) .padding() .frame(maxWidth: .infinity) .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } .padding(.horizontal) } .padding(.bottom) Spacer() } .onTapGesture { hideKeyboard() } .padding(.top, 20) .sheet(isPresented: $showQRCodeScanner) { QRCodeScannerView(discountId: discountId, billAmount: billAmount, discountTitle: voucherTitle, isPresented: $isShowingRedemptionStepView) } .sheet(isPresented: $showPasscodeView) { PasscodeView(billAmount: billAmount, discountId: discountId, discountTitle: voucherTitle, isPresented: $isShowingRedemptionStepView) } .onChange(of: isShowingRedemptionStepView) { oldValue, newValue in if !newValue && oldValue { dismiss() } } .alert("Verify Bill Amount - \(priceToStrFormat(price: (Int(billAmount) ?? 0) * 100, useComma: false))", isPresented: $showAlert) { Button("Proceed\(countdown > 0 ? " In \(countdown)s" : "")") { if countdown == 0 { showQRCodeScanner = true } }.disabled(countdown > 0) Button("Cancel", role: .cancel) { } } message: { Text("Show Phone to Staff to verify bill amount. Proceed in \(countdown) seconds") } // Alert关闭时销毁Timer .onChange(of: showAlert) { _, isShowing in if !isShowing { countdownTimer?.invalidate() countdownTimer = nil } } } private func startCountdown() { // 先销毁旧Timer,避免重复创建 countdownTimer?.invalidate() countdownTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in DispatchQueue.main.async { // 切换到主线程更新@State变量 if countdown > 0 { countdown -= 1 } else { timer.invalidate() countdownTimer = nil } } } } }
关键说明
- 主线程更新:所有
@State变量的修改必须在主线程执行,否则SwiftUI无法检测到变化并刷新视图。 - Timer管理:每次启动倒计时前销毁旧Timer,Alert关闭时也清理Timer,避免内存泄漏和逻辑冲突。
- 重置倒计时:确保每次弹出Alert时,倒计时从初始值5开始,避免复用之前的剩余值。
内容的提问来源于stack exchange,提问作者Alk
相关产品推荐
相关产品推荐

