SwiftUI计时游戏:实现滑块/指示器在目标区域的平滑移动
解决滑块移动卡顿与精准定位问题
核心问题分析
原代码通过Timer每0.01秒手动修改sliderPosition并绑定短时长动画,这种方式会导致:
- 频繁的状态更新触发大量动画重绘,造成卡顿
- Timer触发间隔和动画时长不同步,导致滑块移动不流畅
- 手动边界判断易出现误差,停止时定位不准
优化后的代码
import SwiftUI struct SliderGame: View { @State private var sliderProgress: CGFloat = 0 @State private var isSliding: Bool = false @State private var didWin: Bool = false @State private var didHitYellow: Bool = false let mainRectWidth: CGFloat = 300 let sliderWidth: CGFloat = 10 // 目标区域像素范围 let greenZoneStart: CGFloat = 125 let greenZoneEnd: CGFloat = 175 let yellowZoneStart: CGFloat = 75 let yellowZoneEnd: CGFloat = 225 // 移动周期(秒):数值越小速度越快 let moveCycle: Double = 1.5 var body: some View { VStack { ZStack(alignment: .leading) { // 背景灰色条 Rectangle() .fill(Color.gray) .frame(width: mainRectWidth, height: 50) // 黄色区域 Rectangle() .fill(Color.yellow) .frame(width: yellowZoneEnd - yellowZoneStart, height: 50) .offset(x: yellowZoneStart) // 绿色获胜区域 Rectangle() .fill(Color.green) .frame(width: greenZoneEnd - greenZoneStart, height: 50) .offset(x: greenZoneStart) // 滑块 Rectangle() .fill(Color.blue) .frame(width: sliderWidth, height: 50) .offset(x: sliderProgress * (mainRectWidth - sliderWidth)) } .onTapGesture { guard isSliding else { return } isSliding = false checkResults() } // 结果文本 Group { if didWin { Text("You Win!") .font(.largeTitle) .foregroundColor(.green) } else if didHitYellow { Text("You Hit Yellow!") .font(.largeTitle) .foregroundColor(.yellow) } else if !isSliding { Text("You Lose!") .font(.largeTitle) .foregroundColor(.red) } } // 开始按钮 if !isSliding { Button(action: resetAndStart) { Text("Start") .font(.largeTitle) .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } .padding() } } .padding() } func startSlidingAnimation() { isSliding = true // 用系统原生动画实现往返移动,自动保证流畅性 withAnimation(.linear(duration: moveCycle).repeatForever(autoreverses: true)) { sliderProgress = 1.0 } } func checkResults() { let currentPosition = sliderProgress * (mainRectWidth - sliderWidth) // 判断绿色区域命中 didWin = currentPosition >= greenZoneStart && currentPosition <= greenZoneEnd // 判断黄色区域命中(未中绿色的情况) didHitYellow = !didWin && currentPosition >= yellowZoneStart && currentPosition <= yellowZoneEnd } func resetAndStart() { sliderProgress = 0 didWin = false didHitYellow = false startSlidingAnimation() } } struct SliderGame_Preview: PreviewProvider { static var previews: some View { SliderGame() } }
关键优化点
- 替换Timer为原生动画:用
Animation.repeatForever(autoreverses: true)实现滑块往返,系统自动处理帧动画,彻底解决卡顿问题 - 比例化位置控制:用0-1的
sliderProgress计算滑块位置,避免硬编码像素值,边界判断更精准 - 直观速度调节:通过
moveCycle参数控制速度,数值越小滑块移动越快,调整无需修改多个参数 - 精准停止定位:停止时直接读取当前进度计算位置,避免Timer停止时的状态误差
- 简化逻辑判断:目标区域用起始/结束值直接对比,代码更清晰易维护
额外建议
- 若需要变速效果,可将动画的
.linear替换为.easeInOut等曲线参数 - 可添加难度切换按钮,动态修改
moveCycle值调整速度 - 滑块宽度提取为常量,避免多处硬编码维护成本
内容的提问来源于stack exchange,提问作者David Johansen
相关产品推荐
相关产品推荐

