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

如何让自定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 05:55:28