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

Swift中CABasicAnimation实现的脉冲视图动画无法正常停止如何解决

脉冲动画无法移除问题修复方案

问题根源

  • 你的removeAnimation方法仅移除了子图层上绑定的pulse动画,但是你每次调用pulsate方法时新增的脉冲图层本身没有被从父图层移除,即便动画被移除,残留的图层依然会显示,且后续重复调用pulsate会叠加多个脉冲图层,导致动画无法彻底停止。
  • 原有逻辑没有做重复图层校验,多次调用启动方法会叠加多个无效图层,提升内存占用。

修复后的代码

修改UIView扩展部分

extension UIView {
    // 脉冲图层固定标识,用于精准查找
    private var pulseLayerName: String { "customPulseLayer" }
    
    /// animate the border width
    func animateBorderWidth(toValue: CGFloat, duration: Double) -> CABasicAnimation {
        let widthAnimation = CABasicAnimation(keyPath: "borderWidth")
        widthAnimation.fromValue = 0.9
        widthAnimation.toValue = 0.0
        widthAnimation.duration = duration
        widthAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
        return widthAnimation
    }

    /// animate the scale
    func animateScale(toValue: CGFloat, duration: Double) -> CABasicAnimation {
        let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
        scaleAnimation.fromValue = 1.0
        scaleAnimation.toValue = toValue
        scaleAnimation.duration = duration
        scaleAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
        return scaleAnimation
    }
        
    func pulsate(animationDuration: CGFloat) {
        // 先移除已存在的脉冲层,避免重复添加
        if let existPulseLayer = self.layer.sublayers?.first(where: { $0.name == pulseLayerName }) {
            existPulseLayer.removeFromSuperlayer()
        }
        
        let animationGroup = CAAnimationGroup()
        animationGroup.duration = CFTimeInterval(animationDuration)
        animationGroup.repeatCount = .infinity
        animationGroup.timingFunction = CAMediaTimingFunction(name: .easeOut)
        animationGroup.animations = [
            animateScale(toValue: 7.5, duration: Double(animationDuration)),
            animateBorderWidth(toValue: 0.6, duration: Double(animationDuration))
        ]
        
        let newLayer = CALayer()
        newLayer.name = pulseLayerName // 给图层绑定标识
        newLayer.bounds = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
        newLayer.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
        newLayer.cornerRadius = self.frame.width/2
        newLayer.borderColor = #colorLiteral(red: 0.7215686275, green: 0.5137254902, blue: 0.7647058824, alpha: 0.7).cgColor
        
        newLayer.add(animationGroup, forKey: "pulse")
        self.layer.cornerRadius = self.frame.width/2
        self.layer.insertSublayer(newLayer, at: 0)
    }

    func removeAnimation() {
        // 精准查找脉冲图层
        guard let pulseLayer = self.layer.sublayers?.first(where: { $0.name == pulseLayerName }) else { return }
        // 移除动画
        pulseLayer.removeAnimation(forKey: "pulse")
        // 移除图层本身,彻底清除视觉残留
        pulseLayer.removeFromSuperlayer()
    }
}

原有调用逻辑无需修改

你原来的startPul、stopPul和按钮点击方法不需要做任何调整,修复后调用stopPul即可彻底停止并隐藏动画,再次调用startPul可以重新启动动画。

额外优化点

  • 移除了单个动画中重复的repeatCount、isRemovedOnCompletion配置,动画组的全局配置会覆盖子动画的对应属性,无需重复设置
  • 修复了原代码中动画组duration被重复赋值、子动画duration写死的问题,确保传入的animationDuration参数生效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 15:15:03