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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 15:53:21