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

如何导出带原生动画的SwiftUI View为GIF或PNG序列(无第三方库)

SwiftUI原生动画导出为GIF/带透明通道的PNG序列(无第三方库)

一、导出带透明通道的PNG图片序列

要捕获SwiftUI动画的每帧并保存为PNG,我们可以借助UIHostingController渲染视图,配合CADisplayLink同步屏幕帧率逐帧捕获,核心要保证渲染时保留Alpha通道。

实现代码示例

可以创建一个捕获工具类,或者在ViewController中直接实现:

import UIKit
import SwiftUI

class AnimationCapturer {
    private let hostingController: UIHostingController<AnyView>
    private var displayLink: CADisplayLink?
    private var frameCount = 0
    private let totalFrames: Int
    private let outputURL: URL
    
    // 初始化:传入SwiftUI视图、尺寸、帧率、动画时长、输出目录
    init<Content: View>(view: Content, size: CGSize, fps: Int, duration: TimeInterval, outputDir: URL) {
        self.hostingController = UIHostingController(rootView: AnyView(view.frame(width: size.width, height: size.height)))
        self.hostingController.view.frame = CGRect(origin: .zero, size: size)
        self.hostingController.view.backgroundColor = .clear // 关键:保留透明背景
        
        // 计算总帧数 = 帧率 × 动画时长
        self.totalFrames = Int(fps * duration)
        self.outputURL = outputDir
        
        // 创建输出目录
        try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true)
    }
    
    // 开始捕获
    func startCapturing() {
        frameCount = 0
        displayLink = CADisplayLink(target: self, selector: #selector(captureFrame))
        displayLink?.add(to: .main, forMode: .common)
    }
    
    @objc private func captureFrame() {
        guard frameCount < totalFrames else {
            displayLink?.invalidate()
            displayLink = nil
            print("PNG序列已保存到:\(outputURL.path)")
            return
        }
        
        // 渲染视图为带Alpha通道的UIImage
        UIGraphicsBeginImageContextWithOptions(hostingController.view.bounds.size, false, UIScreen.main.scale)
        hostingController.view.drawHierarchy(in: hostingController.view.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        // 保存为PNG文件
        if let imageData = image?.pngData() {
            let frameURL = outputURL.appendingPathComponent("frame_\(String(format: "%04d", frameCount)).png")
            try? imageData.write(to: frameURL)
        }
        
        frameCount += 1
    }
}

// 使用示例(在ViewController中调用)
class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // 配置捕获参数
        let animationView = SimpleAnimationView()
        let size = CGSize(width: 200, height: 200)
        let fps = 30
        let duration = 0.5 // 对应你的动画单次循环时长
        
        // 获取文档目录作为输出路径
        let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let outputDir = docsDir.appendingPathComponent("AnimationFrames")
        
        // 初始化捕获器并开始捕获
        let capturer = AnimationCapturer(view: animationView, size: size, fps: fps, duration: duration, outputDir: outputDir)
        capturer.startCapturing()
    }
}

// 你的SimpleAnimationView代码
struct SimpleAnimationView: View {
    @State private var isFinalFrame = false
    
    var body: some View {
        GeometryReader { proxy in
            let height = proxy.size.height
            let size = isFinalFrame ? height : height * 0.7
            ZStack {
                Circle()
                    .fill(.orange)
                    .frame(width: size, height: size)
                Color.clear
            }
        }
        .animation(
            .easeInOut(duration: 0.5).repeatForever(),
            value: isFinalFrame
        )
        .task {
            if !isFinalFrame {
                isFinalFrame = true
            }
        }
    }
}

关键注意点

  • 必须设置hostingController.view.backgroundColor = .clear,否则背景会被渲染为黑色,丢失透明通道
  • UIGraphicsBeginImageContextWithOptions的第二个参数传false,表示不忽略Alpha通道
  • 总帧数要匹配动画实际时长和帧率,确保捕获完整的动画循环

二、将PNG序列转为带透明通道的GIF

系统没有提供直接生成GIF的API,但可以用命令行工具ffmpeg完成,无需在代码中引入第三方库。

操作步骤

  1. 安装ffmpeg:通过Homebrew安装(brew install ffmpeg),或下载预编译包
  2. 打开终端,导航到PNG序列所在的目录
  3. 执行以下命令:
ffmpeg -framerate 30 -i frame_%04d.png -disposition 0:default -loop 0 -gifflags +transparency output.gif

参数说明

  • -framerate 30:设置GIF帧率,和捕获时的帧率一致
  • -i frame_%04d.png:指定输入PNG序列的命名格式(和捕获时的命名对应)
  • -loop 0:设置GIF无限循环
  • -gifflags +transparency:保留透明通道
  • output.gif:输出的GIF文件名

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 18:35:38