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

Swift中如何实现UIButton的依次脉动动画序列?

实现UIButton按顺序依次执行脉动动画

没问题,要让按钮按顺序逐个执行脉动动画,核心是得等前一个按钮的动画完全结束后,再触发下一个的动画。这里给你两种实用的方案:

方案一:用动画完成回调(推荐)

首先我们需要给你的pulsate()方法加一个完成回调,这样能精准捕获动画结束的时机。修改你的UIButton扩展如下:

extension UIButton {
    func pulsate(completion: (() -> Void)? = nil) {
        let pulse = CASpringAnimation(keyPath: "transform.scale")
        pulse.duration = 0.3
        pulse.fromValue = 0.95
        pulse.toValue = 1.05
        pulse.autoreverses = true
        pulse.repeatCount = 3
        pulse.initialVelocity = 0.5
        pulse.damping = 1.0
        
        // 存储完成回调,用关联对象避免循环引用
        objc_setAssociatedObject(self, &AssociatedKeys.pulseCompletion, completion, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        pulse.delegate = self
        layer.add(pulse, forKey: "pulse")
    }
    
    private enum AssociatedKeys {
        static var pulseCompletion = "pulseCompletion"
    }
}

// 实现CAAnimationDelegate来监听动画结束
extension UIButton: CAAnimationDelegate {
    public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        guard let completion = objc_getAssociatedObject(self, &AssociatedKeys.pulseCompletion) as? (() -> Void) else { return }
        if flag {
            completion()
        }
        // 清理关联对象,防止内存泄漏
        objc_setAssociatedObject(self, &AssociatedKeys.pulseCompletion, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

然后写一个递归方法来逐个触发按钮动画:

private func animateButtonsSequentially(startingAt index: Int = 0) {
    guard index < buttonsOutlet.count else { return } // 所有按钮动画完成,退出
    
    let currentButton = buttonsOutlet[index]
    currentButton.pulsate { [weak self] in
        // 当前按钮动画结束后,递归调用下一个
        self?.animateButtonsSequentially(startingAt: index + 1)
    }
}

// 闲置检测时调用这个方法
private func onInactivityDetected() {
    animateButtonsSequentially()
}

这种方法的优势是完全基于动画的实际结束时机,哪怕后续你修改了动画的时长、重复次数等参数,也不需要调整其他代码,非常可靠。

方案二:用DispatchQueue延迟触发(简洁版)

如果你不想修改动画扩展的结构,也可以通过手动计算每个按钮的延迟时间来实现顺序动画:

private func onInactivityDetected() {
    // 计算单个按钮动画的总时长:duration * 2(自动反转)* repeatCount
    let singleAnimationTotalTime = 0.3 * 2 * 3
    for (index, button) in buttonsOutlet.enumerated() {
        let delay = Double(index) * singleAnimationTotalTime
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            button.pulsate()
        }
    }
}

这种方法代码更短,但缺点是依赖手动计算总时长,如果后续修改了动画的参数(比如把repeatCount改成2),必须同步更新这里的计算,不然会出现动画重叠或者间隔不对的问题。


内容的提问来源于stack exchange,提问作者John Smith Optional

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:31:23