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
相关产品推荐
相关产品推荐

