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

SwiftUI带倒计时的Alert:如何动态更新Alert中使用的变量

SwiftUI Alert倒计时不更新问题修复

问题

在RedemptionView视图中,期望Alert能跟随countdown变量动态更新(该变量同时控制Proceed按钮的激活状态和Alert文本内容),但当前实现中countdown始终停留在初始值5,无法实时更新。

原因

  1. 线程问题:Timer.scheduledTimer默认在后台线程执行,而SwiftUI的@State变量必须在主线程更新才能触发视图刷新,后台线程修改不会让Alert重新渲染。
  2. 未重置倒计时:每次弹出Alert时没有重置countdown为初始值,可能导致复用旧值。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 11:57:18