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

SwiftUI中更新值后.repeatForever()动画自动反转失效问题

SwiftUI闲置动画问题:自动反转功能失效

我想实现一个闲置触发的按钮动画:屏幕3秒无触摸时,按钮沿Y轴偏移15pt并循环往返(自动反转);触摸屏幕时,按钮立即回到初始位置。但当前实现存在问题:按钮回到原位后,再次进入闲置状态时,动画的autoreverses功能不再生效,按钮只会停在偏移15pt的位置,无法往返运动。

原实现代码

GoLiveButton组件代码

struct GoLiveButton: View {

  @State private var animationOffset: CGFloat = 0
  @Binding var isIdle: Bool

  var body: some View {
    ZStack {
      Button(action: {} ) {
        Text("Go Live")
          .frame(width: 120, height: 40)
          .background(Color.black)
          .foregroundColor(.white)
          .clipShape(Capsule())
          .font(.system(size: 20))
          .shadow(color: .black, radius: 4, x: 4, y: 4)
      }
      .offset(y: animationOffset)
      .animation(.timingCurve(0.38, 0.07, 0.12, 0.93, duration: 2).repeatForever(autoreverses: true), value: isIdle)
      .animation(.timingCurve(0.38, 0.07, 0.12, 0.93, duration: 2), value: !isIdle)
    }
    .onAppear {
      self.isIdle = true
      self.animationOffset = 15
    }
    .onChange(of: isIdle) { newValue in
      if newValue {
        self.animationOffset = 15
      }
      else {
        self.animationOffset = 0
      }
    }
  }
}

闲置检测视图代码

struct StackOverflowView: View {

  @State private var timer: Timer?
  @State private var isIdle = false

  var body: some View {

    GeometryReader { geo in
      GoLiveButton(isIdle: $isIdle)
    }
    .onTapGesture {
      print("DEBUG: CustomTabView OnTapGesture Triggered")
      self.isIdle = false
      self.timer?.invalidate()
      self.timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
        self.isIdle = true
      }
    }
    .gesture(
      DragGesture().onEnded { _ in
        self.isIdle = false
        self.timer?.invalidate()
        self.timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
          self.isIdle = true
        }
      }
    )
  }
}

修复方案

问题核心在于:原代码通过绑定isIdle状态直接触发重复动画,状态切换时SwiftUI的动画系统无法正确重置循环状态;同时多动画绑定导致逻辑冲突。以下是修复后的代码:

修改后的GoLiveButton组件

struct GoLiveButton: View {
    @State private var animationOffset: CGFloat = 0
    @Binding var isIdle: Bool
    
    // 抽离闲置动画常量,统一配置
    private let idleAnimation = Animation.timingCurve(0.38, 0.07, 0.12, 0.93, duration: 2)
        .repeatForever(autoreverses: true)
    
    var body: some View {
        Button(action: {}) {
            Text("Go Live")
                .frame(width: 120, height: 40)
                .background(Color.black)
                .foregroundColor(.white)
                .clipShape(Capsule())
                .font(.system(size: 20))
                .shadow(color: .black, radius: 4, x: 4, y: 4)
        }
        .offset(y: animationOffset)
        .onAppear {
            startIdleAnimation()
        }
        .onChange(of: isIdle) { newValue in
            if newValue {
                startIdleAnimation()
            } else {
                // 快速回到初始位置,可自定义动画速度
                withAnimation(idleAnimation.speed(2)) {
                    animationOffset = 0
                }
            }
        }
    }
    
    // 手动触发循环动画,确保动画系统正确初始化
    private func startIdleAnimation() {
        animationOffset = 0
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
            withAnimation(idleAnimation) {
                animationOffset = 15
            }
        }
    }
}

简化后的闲置检测视图

struct StackOverflowView: View {
    @State private var timer: Timer?
    @State private var isIdle = false
    
    var body: some View {
        GeometryReader { _ in
            GoLiveButton(isIdle: $isIdle)
        }
        .onTapGesture(perform: resetIdleTimer)
        .gesture(
            DragGesture().onEnded { _ in resetIdleTimer() }
        )
    }
    
    // 抽离重置计时器逻辑,避免代码重复
    private func resetIdleTimer() {
        print("DEBUG: Interaction detected")
        isIdle = false
        timer?.invalidate()
        timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
            isIdle = true
        }
    }
}

修复说明

  1. 将循环动画抽为常量,避免重复定义导致的配置不一致
  2. 通过startIdleAnimation方法手动触发动画:先重置偏移量,再延迟启动循环动画,确保SwiftUI动画系统能正确初始化循环状态
  3. 退出闲置时使用withAnimation控制返回动画,可自定义速度提升交互体验
  4. 简化闲置检测视图的逻辑,抽离重复代码,提升可读性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 16:16:10