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

SwiftUI如何创建高性能逐帧动画ImageView?

背景

我正在将一款Apple Watch应用从WatchKit迁移至SwiftUI。该应用原本借助WKInterfaceImage实现高性能的逐帧动画,循环展示一系列图片。

应用包含16张PNG图片,每张约8KB、256×256像素,循环时长为0.66秒。

资源目录中包含完整的图片压缩包。

实现方案

为此我创建了一个简单的FrameAnimatedImageView组件,代码如下:

struct FrameAnimatedImageView: View{
    
    let imageName: String
    let frameCount: Int
    let duration: TimeInterval

    @State private var currentFrameIndex: Int
    
    private var currentImageName: String {
        "\(imageName)\(currentFrameIndex)"
    }
    
    @State private var timer: Timer?
    
    init(imageName: String, frameCount: Int, duration: TimeInterval) {
        self.imageName = imageName
        self.frameCount = frameCount
        self.duration = duration
        
        self.currentFrameIndex = 0
    }
    
    var body: some View {
        Image(currentImageName)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .onAppear(perform: startAnimationTimer)
            .onDisappear(perform: stopAnimationTimer)
    }
    
    private func startAnimationTimer() {
        stopAnimationTimer()
        timer = Timer(timeInterval: duration / Double(frameCount), repeats: true) { (Timer) in
            currentFrameIndex = (currentFrameIndex + 1) % frameCount
        }
        RunLoop.main.add(timer!, forMode: .common)
    }
    
    private func stopAnimationTimer() {
        timer?.invalidate()
        timer = nil
    }
    
}
问题

该实现能达到相同的动画效果,但CPU占用率显著更高(在Apple Watch S6上约占15%,而WKInterfaceImage动画占用率不足1%)。

通过性能分析发现,SwiftUI重绘是主要耗时操作,其次是图片加载。基于此我做了小幅优化,使用UIImage(及其内置缓存)来填充SwiftUI Image视图:

struct FrameAnimatedImageView: View{
    
    let imageName: String
    let frameCount: Int
    let duration: TimeInterval

    @State private var currentFrameIndex: Int
    
    private var currentImageName: String {
        "\(imageName)\(currentFrameIndex)"
    }
    
    private var currentImage: UIImage { // 小幅优化
        UIImage(named: currentImageName)!
    }
    
    
    @State private var timer: Timer?
    
    init(imageName: String, frameCount: Int, duration: TimeInterval) {
        self.imageName = imageName
        self.frameCount = frameCount
        self.duration = duration
        
        self.currentFrameIndex = 0
    }
    
    var body: some View {
        Image(uiImage: currentImage) // 小幅优化
            .resizable()
            .aspectRatio(contentMode: .fit)
            .onAppear(perform: startAnimationTimer)
            .onDisappear(perform: stopAnimationTimer)
    }
    
    private func startAnimationTimer() {
        stopAnimationTimer()
        timer = Timer(timeInterval: duration / Double(frameCount), repeats: true) { (Timer) in
            currentFrameIndex = (currentFrameIndex + 1) % frameCount
        }
        RunLoop.main.add(timer!, forMode: .common)
    }
    
    private func stopAnimationTimer() {
        timer?.invalidate()
        timer = nil
    }
    
}

@available(watchOSApplicationExtension 7.0, *)
struct FrameAnimatedImageView_Previews: PreviewProvider {
    static var previews: some View {
        FrameAnimatedImageView(imageName: "running", frameCount: 16, duration: 0.66)
    }
}

优化后的性能仍不理想(该动画仍占用手表10-15%的CPU),我并不希望以此方案发布应用。

尝试过的替代方案

我已尝试/考虑过以下方案:

  • 通过WKInterfaceObjectRepresentable包装WKImageView
    此方案不可行,因为WKImageView未提供公开的初始化方法。
  • 使用SwiftUI Canvas绘制图片
    不使用SwiftUI Image,而是用SwiftUI Canvas绘制图片。初步尝试后发现CPU占用并未降低,反而略有升高。
  • 创建UIImage animatedImage并传入SwiftUI Image
    利用UIImage内置的动画支持创建单个UIImage对象,再传入SwiftUI Image视图。但此方案无效,SwiftUI仅显示第一帧。
  • 其他方案?
    我不确定是否存在更优的实现方案,特此寻求帮助。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 17:42:25