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

SwiftUI中Timer与Double类型的if判断问题:兼顾进度条流畅性与触发准确性

倒计时Timer的条件触发与进度条流畅性解决方案

问题描述

我在App里用每0.1秒触发一次的Timer实现流畅的进度条动画,同时加了两个条件:倒计时到10秒时播放音效,到0秒时停止Timer。但现在条件完全触发不了;如果把countdownTimer转成Int类型,条件会连续触发10次;换成每秒触发的Int Timer,进度条又会卡顿。怎么在保持进度条流畅的同时解决这个问题?

附带原代码:

struct TimerComponent: View {
    
    @State var progress: Double = 0
    @Binding var countdownTimer: Double
    @Binding var timerRunning: Bool
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        
        
        ZStack {
            
            CircularProgressComponent(progress: progress, timerRunning: $timerRunning)
                .frame(width: 120, height: 120)
            
            Text("\(Int(countdownTimer))")
                .onReceive(timer){ _ in
                    if countdownTimer > 0 && timerRunning {
                        countdownTimer -= 0.1
                        progress = 1 - Double(countdownTimer)/60
                        if countdownTimer == 10.0 {
                            SoundManager.instance.playSound(sound: .flatline)
                        }
                    } else if countdownTimer == 0.0 {
                        timerRunning = false
                        countdownTimer = -1
                    }
                }
                .font(Font.custom("AnnieUseYourTelescope-Regular", size: 40))
                .frame(minWidth: 80, maxHeight: 50)
                .padding(10)
                .foregroundColor(.black)
                .cornerRadius(10)
                .shadow(radius: 50)
        }
        
    }
} 

核心问题分析

  • 浮点数精度误差:直接用countdownTimer == 10.0或countdownTimer == 0.0判断会失效——每次减0.1的操作多次累积后,数值会变成类似9.999999999999998或0.000000000000001的近似值,永远不等于精确整数。
  • 重复触发逻辑:转成Int后,0.1秒触发一次的Timer会在10秒到9.1秒之间连续10次让Int(countdownTimer)等于10,导致音效重复播放。

解决方案

方案1:阈值范围判断(简单直接)

放弃精确等于的判断,改用数值落在极小范围内来判定是否到达目标时间,同时添加标记避免重复触发:

struct TimerComponent: View {
    
    @State var progress: Double = 0
    @Binding var countdownTimer: Double
    @Binding var timerRunning: Bool
    @State private var hasPlayedTenSecondSound = false // 新增:标记音效是否已播放
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            CircularProgressComponent(progress: progress, timerRunning: $timerRunning)
                .frame(width: 120, height: 120)
            
            Text("\(Int(countdownTimer))")
                .onReceive(timer){ _ in
                    if countdownTimer > 0 && timerRunning {
                        countdownTimer -= 0.1
                        progress = 1 - countdownTimer/60
                        
                        // 用阈值范围判断是否接近10秒,且仅播放一次
                        if countdownTimer < 10.1 && countdownTimer > 9.9 && !hasPlayedTenSecondSound {
                            SoundManager.instance.playSound(sound: .flatline)
                            hasPlayedTenSecondSound = true
                        }
                    } else if countdownTimer < 0.1 { // 用阈值判断是否接近0秒
                        timerRunning = false
                        countdownTimer = -1
                    }
                }
                .font(Font.custom("AnnieUseYourTelescope-Regular", size: 40))
                .frame(minWidth: 80, maxHeight: 50)
                .padding(10)
                .foregroundColor(.black)
                .cornerRadius(10)
                .shadow(radius: 50)
        }
    }
}

方案2:跟踪整秒数变化(精准控制单次触发)

添加状态变量记录上一次的整秒数值,仅当整秒数发生变化且等于目标值时触发逻辑:

struct TimerComponent: View {
    
    @State var progress: Double = 0
    @Binding var countdownTimer: Double
    @Binding var timerRunning: Bool
    @State private var lastDisplayedSecond: Int = -1 // 新增:记录上一次的整秒数
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            CircularProgressComponent(progress: progress, timerRunning: $timerRunning)
                .frame(width: 120, height: 120)
            
            Text("\(Int(countdownTimer))")
                .onReceive(timer){ _ in
                    guard countdownTimer > 0 && timerRunning else {
                        if countdownTimer <= 0.1 {
                            timerRunning = false
                            countdownTimer = -1
                        }
                        return
                    }
                    
                    countdownTimer -= 0.1
                    progress = 1 - countdownTimer/60
                    
                    let currentSecond = Int(countdownTimer.rounded()) // 取当前整秒数
                    if currentSecond != lastDisplayedSecond {
                        lastDisplayedSecond = currentSecond
                        // 仅当整秒数变化到10时触发音效
                        if currentSecond == 10 {
                            SoundManager.instance.playSound(sound: .flatline)
                        }
                    }
                }
                .font(Font.custom("AnnieUseYourTelescope-Regular", size: 40))
                .frame(minWidth: 80, maxHeight: 50)
                .padding(10)
                .foregroundColor(.black)
                .cornerRadius(10)
                .shadow(radius: 50)
        }
    }
}

方案优势

  • 两种方案都保留了0.1秒间隔的Timer,确保进度条动画流畅;
  • 方案1实现简单,适合快速解决问题;
  • 方案2通过跟踪整秒数变化,完全避免重复触发,逻辑更精准。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:20:28