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
相关产品推荐
相关产品推荐

