SwiftUI触发repeat动画时被切换的布尔值最终始终为true如何解决
问题原因
你对SwiftUI的repeatCount动画的作用逻辑存在误解:repeatCount只会重复动画过渡效果,不会重复执行withAnimation闭包内的代码。你在闭包里只调用了一次isAnimating.toggle(),所以无论设置多少次重复,isAnimating的取值只会从初始的false变更为true一次,重复的只是「蓝色到红色」的过渡动画,动画结束后状态自然停留在true,这就是最后背景始终为红色的核心原因。
另外你现有代码中shouldAnimate标记位赋值为true后没有复位,后续再次触发动画时会无法进入判断分支,也属于需要修复的问题。
现有实现方案的缺陷
你用DispatchQueue.main.asyncAfter循环调度的方案虽然能达到效果,但存在两个明显问题:
- 动画时长和调度间隔强绑定,后续修改动画参数需要同步调整调度逻辑,维护成本高
- 动画过程中如果页面被销毁,未执行的调度任务仍然会触发,可能出现野访问异常
更合理的实现方案
方案1:使用autoreverses配合延迟复位(最简洁)
利用动画自带的往返效果,仅需要两次状态变更即可实现需求,代码改动最小:
首先修改动画定义:
struct Constants { static let animationDuration = 1.0 static let pulseRepeatTimes = 3 // 脉冲次数,对应6次颜色切换 static let totalAnimationDuration = animationDuration * 2 * Double(pulseRepeatTimes) static let backgroundAlpha: CGFloat = 0.6 } // 自动往返的重复动画 private let animation = Animation.easeInOut(duration: Constants.animationDuration).repeatCount(Constants.pulseRepeatTimes, autoreverses: true)
然后修改动画触发逻辑:
.onReceive(viewModel.$shouldAnimate, perform: { _ in guard viewModel.shouldAnimate else { return } // 先复位标记位,方便下次触发 viewModel.shouldAnimate = false // 触发动画 withAnimation(animation) { isAnimating = true } // 动画结束后自动复位状态 DispatchQueue.main.asyncAfter(deadline: .now() + Constants.totalAnimationDuration) { isAnimating = false } })
该方案不需要循环调度,逻辑清晰,维护成本极低,适合简单的脉冲动画场景。
方案2:使用Timer序列控制状态切换(可控性最高)
如果需要对每一次颜色切换做自定义控制,可以用Combine的Timer Publisher实现,不需要手动管理多个调度任务:
.onReceive(viewModel.$shouldAnimate, perform: { _ in guard viewModel.shouldAnimate else { return } viewModel.shouldAnimate = false // 生成6次定时触发事件,间隔为单段动画时长 Timer.publish(every: Constants.animationDuration, on: .main, in: .common) .autoconnect() .prefix(6) // 触发6次后自动结束 .sink { [weak self] _ in withAnimation(.easeInOut(duration: Constants.animationDuration)) { self?.isAnimating.toggle() } } .store(in: &viewModel.cancellables) })
该方案会自动在触发指定次数后取消订阅,不存在内存泄漏风险,灵活度更高,适合需要自定义每段动画逻辑的场景。
内容的提问来源于stack exchange,提问作者DevB1
相关产品推荐
相关产品推荐

