如何让自定义UIView中的iOS Swift块动画持续循环运行?
视图动画脱离生命周期限制实现持续循环运行
现有代码
AnimationView.swift
import Foundation import UIKit class AnimationView: UIView { private let backgroundView = UIView() private let foregroundView = UIView() override init(frame: CGRect) { super.init(frame: frame) // 原有初始化代码... } // 其他原有代码... }
ViewController.swift
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override func viewDidAppear(_ animated: Bool) { let x = AnimationView(frame: .zero) // 原有添加视图代码... } }
问题描述
当前使用UIView.animate实现的缩放循环动画存在依赖视图控制器生命周期的问题,只有在视图加入视图层级后调用动画代码,动画才能正常运行。需要让该缩放动画不受这些因素影响,持续循环运行。
当前动画代码:
UIView.animate(withDuration: 2.0, delay: 0, options: [.autoreverse, .repeat], animations: { self.foregroundView.transform = CGAffineTransform(scaleX: 0.3, y: 0.3) }, completion: nil)
动画效果截图

解决方案
方案一:使用CAKeyframeAnimation实现独立循环动画
直接基于CALayer添加动画,脱离UIView动画队列的限制,只要图层初始化完成,动画就会自动运行,不受视图是否加入层级的影响。
在AnimationView中添加动画设置方法,并在初始化时调用:
class AnimationView: UIView { private let backgroundView = UIView() private let foregroundView = UIView() override init(frame: CGRect) { super.init(frame: frame) setupSubviews() setupLoopAnimation() // 初始化时启动动画 } required init?(coder: NSCoder) { super.init(coder: coder) setupSubviews() setupLoopAnimation() } private func setupSubviews() { // 原有子视图布局代码... addSubview(backgroundView) addSubview(foregroundView) // 约束设置... } private func setupLoopAnimation() { // 创建缩放关键帧动画 let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale") // 动画值:初始大小 → 缩小到0.3 → 回到初始大小 scaleAnimation.values = [1.0, 0.3, 1.0] // 单次动画时长(包含往返) scaleAnimation.duration = 2.0 // 无限重复 scaleAnimation.repeatCount = .infinity // 添加到前景视图图层 foregroundView.layer.add(scaleAnimation, forKey: "foregroundScaleLoop") } // 其他原有代码... }
方案二:使用CADisplayLink手动驱动动画
如果需要更精细的动画控制,可使用CADisplayLink逐帧更新视图状态,完全脱离系统动画队列的限制:
class AnimationView: UIView { private let backgroundView = UIView() private let foregroundView = UIView() private var displayLink: CADisplayLink? private var elapsedTime: TimeInterval = 0 private let animationCycleDuration: TimeInterval = 2.0 // 一次往返的时长 override init(frame: CGRect) { super.init(frame: frame) setupSubviews() startDisplayLinkAnimation() } required init?(coder: NSCoder) { super.init(coder: coder) setupSubviews() startDisplayLinkAnimation() } private func setupSubviews() { // 原有子视图布局代码... } private func startDisplayLinkAnimation() { displayLink = CADisplayLink(target: self, selector: #selector(updateAnimationFrame)) displayLink?.add(to: .main, forMode: .common) } @objc private func updateAnimationFrame() { guard let link = displayLink else { return } elapsedTime += link.duration // 计算当前动画周期内的进度(0~1代表一次完整的往返) let progress = (elapsedTime.truncatingRemainder(dividingBy: animationCycleDuration)) / animationCycleDuration let scale: CGFloat if progress <= 0.5 { // 前半段:从1缩小到0.3 scale = 1.0 - 0.7 * (progress * 2) } else { // 后半段:从0.3放大回1 scale = 0.3 + 0.7 * ((progress - 0.5) * 2) } foregroundView.transform = CGAffineTransform(scaleX: scale, y: scale) } deinit { // 销毁displayLink避免内存泄漏 displayLink?.invalidate() } // 其他原有代码... }
核心说明
- 原
UIView.animate依赖UIKit的动画队列,只有视图处于视图层级中时,队列才会被系统调度执行,因此会受生命周期影响。 - CAAnimation直接作用于CALayer,动画由Core Animation引擎管理,只要图层存在,动画就会持续运行(视图加入层级后即可看到效果)。
- 使用CADisplayLink时,需在视图销毁时调用
invalidate()释放资源,避免内存泄漏。
内容的提问来源于stack exchange,提问作者AlanSTACK
相关产品推荐
相关产品推荐

