SwiftUI长按触发按钮动画实现问题及代码排查
解决长按动画按钮GestureState不更新的问题及需求实现
为什么topG从未触发更新?
- LongPressGesture的状态特性:
LongPressGesture的updating闭包里的cstate只有在长按达到设置的minimumDuration(你代码里是1.5秒)后才会变为true,在这之前始终是false,而@GestureState private var topG = false的初始值就是false,状态没有发生变化,自然不会触发更新。 - 重复动画修饰符冲突:你给同一个视图添加了两个针对
topG的animation修饰符,这会导致动画逻辑冲突,可能掩盖状态变化的效果。
实现目标效果的修改代码
struct ContentView: View { var body: some View { MyCustomButton(text: "My button") { print("button pressed") } .padding(100) } } struct MyCustomButton: View { let text: LocalizedStringKey var action: () -> () // 标记是否已触发action,避免重复执行 @State private var actionTriggered = false // 管理触摸过程中的按压状态 @GestureState private var isPressing = false // 标记长按是否完成 @State private var longPressCompleted = false private let radius: CGFloat = 10 // 长按触发时长(可调整为0.7秒) private let longPressDuration: Double = 1.5 // 记录触摸开始时间,用于计算进度 @State private var gestureStartTime = Date() var body: some View { Text(text) .padding(.horizontal, 30) .padding(.vertical, 10) .background { Bg() } // 根据按压状态应用缩放动画,释放时用更快的恢复动画 .scaleEffect(isPressing ? 1.2 : 1) .animation(isPressing ? .easeInOut.speed(0.5) : .easeOut(duration: 0.2), value: isPressing) .gesture( // 用DragGesture捕获触摸开始/结束,同时结合LongPressGesture检测长按完成 DragGesture(minimumDistance: 0) .onChanged { _ in // 仅在触摸开始时记录时间 if !isPressing { gestureStartTime = Date() } } .updating($isPressing) { _, state, _ in // 触摸开始立即触发按压状态 state = true } .onEnded { _ in // 触摸结束后重置状态,触发恢复动画 longPressCompleted = false actionTriggered = false } .simultaneously(with: LongPressGesture(minimumDuration: longPressDuration, maximumDistance: 10) .onEnded { _ in // 仅在未触发过action时执行 guard !actionTriggered else { return } actionTriggered = true longPressCompleted = true action() // 延迟重置状态,保证动画完整显示 DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { longPressCompleted = false } } ) ) } func Bg() -> some View { RoundedRectangle(cornerRadius: radius) .fill(Color.black.opacity(0.4)) .overlay{ ZStack { RoundedRectangle(cornerRadius: radius, style: .continuous) .foregroundColor(.gray.opacity(0.1)) RoundedRectangle(cornerRadius: radius, style: .continuous) // 根据按压状态和长按进度调整描边范围 .trim(from: 0, to: isPressing ? (longPressCompleted ? 1 : min(1, Date().timeIntervalSince(gestureStartTime)/longPressDuration)) : 0) .stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round)) .rotation(Angle(degrees: 180)) .animation(.easeInOut(duration: longPressDuration), value: longPressCompleted) } } } }
关键修改说明
- 触摸即时响应:用
DragGesture(minimumDistance: 0)捕获触摸开始事件,立即触发按压动画,解决原代码中触摸无反馈的问题。 - 长按触发逻辑:通过
simultaneously(with:)组合手势,长按达到设定时长后自动执行action,用actionTriggered标记避免重复触发。 - 区分动画时长:未触发action时,释放动画采用0.2秒的快速恢复;按压过程使用慢动作动画,符合需求差异。
- 进度动画处理:实时计算长按进度,长按完成后直接拉满描边进度,触发action后延迟重置状态,保证动画完整展示。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

