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

iOS动画流程实现:应编写哪些完成处理程序?

嘿,我太懂这种“懂原理但不知道怎么落地”的感觉了!刚好你这个嵌套VC+SpriteKit动画的场景非常典型,我来给你把代码里的???填上,同时完美实现「提前准备动画+按顺序执行」的需求。

首先明确核心思路:我们用串行队列处理动画资源的提前准备(避免阻塞主线程),同时用SpriteKit的动作完成回调保证动画顺序执行——前一个动画跑完再启动下一个,而且在前一个动画运行时,后台已经在准备下一个动画的资源了。

假设你的AnimationVC基础代码框架是这样的,我直接补全???内容,再给你拆解每个部分的作用:

import SpriteKit

class AnimationVC: UIViewController {
    @IBOutlet weak var skView: SKView!
    private var currentScene: SKScene!
    
    // ??? 这里需要工具管理动画顺序和后台准备
    // 👇 补全:串行队列,保证准备工作按顺序执行且不卡主线程
    private let animationPrepareQueue = DispatchQueue(label: "com.yourApp.animationPrepareQueue", qos: .userInteractive)
    // 标记当前是否有动画在运行,防止重复触发
    private var isAnimating = false

    override func viewDidLoad() {
        super.viewDidLoad()
        setupSpriteScene()
        // 启动动画序列
        startAnimationSequence()
    }

    private func setupSpriteScene() {
        currentScene = SKScene(size: skView.bounds.size)
        currentScene.backgroundColor = .black
        skView.presentScene(currentScene)
        // 添加测试用动画节点
        let testNode = SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))
        testNode.name = "animationTarget"
        testNode.position = CGPoint(x: 50, y: currentScene.size.height/2)
        currentScene.addChild(testNode)
    }

    private func startAnimationSequence() {
        // ??? 启动第一个动画并衔接后续流程
        // 👇 补全:先准备第一个动画,准备好就执行,执行完衔接下一个
        prepareMoveAnimation { [weak self] moveAction in
            self?.executeAnimation(with: moveAction) {
                // 第一个动画运行时,后台开始准备第二个动画
                self?.animationPrepareQueue.async {
                    self?.prepareScaleAnimation { scaleAction in
                        self?.executeAnimation(with: scaleAction) {
                            // 第二个动画运行时,后台准备第三个动画
                            self?.animationPrepareQueue.async {
                                self?.prepareFadeAnimation { fadeAction in
                                    self?.executeAnimation(with: fadeAction) {
                                        print("所有动画序列执行完成!")
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // 准备第一个动画:移动节点
    private func prepareMoveAnimation(completion: @escaping (SKAction) -> Void) {
        animationPrepareQueue.async {
            // 这里可做耗时准备:比如加载纹理、计算复杂路径
            let moveAction = SKAction.moveTo(x: self.currentScene.size.width - 50, duration: 2)
            // 准备好后把动作传给执行方法
            completion(moveAction)
        }
    }

    // ??? 执行动画并处理完成回调
    // 👇 补全:SpriteKit操作必须在主线程,所以切回主队列
    private func executeAnimation(with action: SKAction, completion: @escaping () -> Void) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self, let targetNode = self.currentScene.childNode(withName: "animationTarget") else { return }
            self.isAnimating = true
            // 把动画动作和完成回调打包成序列
            let completionAction = SKAction.run(completion)
            let animationSequence = SKAction.sequence([action, completionAction])
            // 让节点运行这个序列
            targetNode.run(animationSequence) {
                self.isAnimating = false
            }
        }
    }

    // 准备第二个动画:缩放节点
    private func prepareScaleAnimation(completion: @escaping (SKAction) -> Void) {
        animationPrepareQueue.async {
            // 提前准备缩放动作(可在这里加载纹理帧等耗时操作)
            let scaleUp = SKAction.scale(to: 1.5, duration: 1)
            let scaleDown = SKAction.scale(to: 1.0, duration: 1)
            let scaleSequence = SKAction.sequence([scaleUp, scaleDown])
            completion(scaleSequence)
        }
    }

    // 准备第三个动画:淡出并移除节点
    private func prepareFadeAnimation(completion: @escaping (SKAction) -> Void) {
        animationPrepareQueue.async {
            let fadeOut = SKAction.fadeOut(withDuration: 1)
            let removeNode = SKAction.removeFromParent()
            let endSequence = SKAction.sequence([fadeOut, removeNode])
            completion(endSequence)
        }
    }
}

关键细节说明:

  1. 串行队列animationPrepareQueue:用来处理所有动画的准备工作(比如加载纹理、计算路径),耗时操作在后台串行执行,既不阻塞主线程导致UI卡顿,又能保证准备顺序和动画执行顺序一致。
  2. executeAnimation方法:SpriteKit的UI相关操作必须在主线程,所以这里强制切回主队列。同时我们把动画动作和完成回调打包成SKAction.sequence,确保动画完全结束后才触发下一个动画的执行。
  3. 提前准备的实现:在前一个动画的completion闭包里,立刻在后台队列启动下一个动画的准备——也就是说,前一个动画还在运行时,下一个动画的资源已经在后台准备好了,等前一个跑完就能立刻启动,完全符合你的需求。

优化建议(避免回调地狱):

如果动画序列很长,嵌套闭包会显得杂乱,可以把动画准备逻辑封装成数组,用递归遍历执行:

// 定义动画准备方法的数组
private let animationPreparers: [( @escaping (SKAction) -> Void ) -> Void] = [
    prepareMoveAnimation,
    prepareScaleAnimation,
    prepareFadeAnimation
]

// 递归执行动画序列
private func runNextAnimation(at index: Int = 0) {
    guard index < animationPreparers.count else {
        print("所有动画完成!")
        return
    }
    let currentPreparer = animationPreparers[index]
    currentPreparer { [weak self] action in
        self?.executeAnimation(with: action) {
            // 当前动画执行时,后台准备下一个
            self?.animationPrepareQueue.async {
                self?.runNextAnimation(at: index + 1)
            }
        }
    }
}

// 启动时直接调用:runNextAnimation()

这样代码更整洁,扩展性也更强~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:53:05