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

SwiftUI Transactions实现:基于外部状态控制按钮动画启停

解决外部状态控制SwiftUI永久重复动画抖动问题

问题背景

需要实现一个由外部视图状态控制、可条件触发永久重复动画的视图。原内部状态控制的按钮动画正常,但改为外部绑定状态并使用Transactions API时,点击控制按钮出现异常抖动。

原代码回顾

内部状态控制版本(正常工作)

// 内部状态控制的按钮动画代码
struct ButtonBouncerView: View {
    @State var active: Bool = false

    var body: some View {
        Circle()
            .scaleEffect(active ? 1.08: 1)
            .animation(Animation.default.repeatForever(autoreverses: true), value: active)
            .frame(width: 100, height: 100)
            .onTapGesture {
                useTransaction()
            }
            .onAppear {
                active = true
            }
    }

    func useTransaction() {
        var transaction = Transaction()
        transaction.disablesAnimations = active ? true : false

        withTransaction(transaction) {
            withAnimation {
                active.toggle()
            }
        }
    }
}

外部状态控制尝试版本(存在抖动问题)

// 尝试的外部状态控制实现代码
struct OutsideControllableButtonView: View {
    @State private var active: Bool = false

    var body: some View {
        VStack {
            OutsideControllableButtonScaler(active: $active)
            Button(action: { // CONTROLLER BUTTON!
                active.toggle()
            }) {
                Text("Control Button")
            }
            .padding()
            .background(Color.green)
            .foregroundColor(.white)
        }
    }
}

struct OutsideControllableButtonScaler: View {
    // 激活时按钮执行动画
    @Binding var active: Bool

    var body: some View {
        Circle()
            .scaleEffect(active ? 1.08: 1)
            .animation(Animation.default.repeatForever(autoreverses: true), value: active)
            .frame(width: 100, height: 100)
            .onChange(of: active) { newValue in
                var transaction = Transaction(animation: newValue ? Animation.default.repeatForever(autoreverses: true) : nil)
                transaction.disablesAnimations = true
                withTransaction(transaction) {
                    self.active = newValue
                }
            }
    }
}

问题原因

  1. 子视图的onChange回调中重复修改@Binding的active值,引发循环更新,导致视图反复重绘出现抖动。
  2. 错误设置transaction.disablesAnimations = true,与指定的永久重复动画规则冲突,造成动画状态混乱。

修正方案

方案1:外部控制器统一管理动画事务

将动画逻辑移到外部控制按钮中,子视图仅负责响应状态变化:

struct OutsideControllableButtonView: View {
    @State private var active: Bool = false

    var body: some View {
        VStack {
            OutsideControllableButtonScaler(active: $active)
            Button(action: {
                var transaction = Transaction()
                // 激活时使用永久重复动画,关闭时直接恢复无动画
                transaction.animation = active ? nil : Animation.default.repeatForever(autoreverses: true)
                withTransaction(transaction) {
                    active.toggle()
                }
            }) {
                Text("Control Button")
            }
            .padding()
            .background(Color.green)
            .foregroundColor(.white)
        }
    }
}

struct OutsideControllableButtonScaler: View {
    @Binding var active: Bool

    var body: some View {
        Circle()
            .scaleEffect(active ? 1.08: 1)
            // 依赖外部传入的事务动画更新
            .animation(.default, value: active)
            .frame(width: 100, height: 100)
    }
}

方案2:子视图根据状态自动切换动画

简化外部逻辑,子视图直接根据active状态应用对应动画:

struct OutsideControllableButtonView: View {
    @State private var active: Bool = false

    var body: some View {
        VStack {
            OutsideControllableButtonScaler(active: $active)
            Button(action: {
                active.toggle()
            }) {
                Text("Control Button")
            }
            .padding()
            .background(Color.green)
            .foregroundColor(.white)
        }
    }
}

struct OutsideControllableButtonScaler: View {
    @Binding var active: Bool

    var body: some View {
        Circle()
            .scaleEffect(active ? 1.08: 1)
            // 激活时用永久重复动画,关闭时用默认动画恢复
            .animation(
                active ? Animation.default.repeatForever(autoreverses: true) : .default,
                value: active
            )
            .frame(width: 100, height: 100)
    }
}

效果说明

两种方案都能实现:

  • 点击控制按钮激活时,圆形视图开始持续缩放的重复动画
  • 再次点击关闭时,视图立即恢复原始大小(或平滑恢复,根据方案选择)
  • 无抖动、无异常动画状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 05:14:54