如何使用 SwiftUI 为一系列图像制作动画?

How to animate a sequence of images using SwiftUI?

如何使用 SwiftUI 框架为一系列图像(比如第 3 帧 1.png 到第 6 帧)设置动画?

我试过创建 "images" 的数组。然后我将 UIImage.animatedImage(with: images, duration: 1.0) 方法分配给一个名为 "animatedImage" 的变量 最后我在 "ContentView.swift"

的 "body" 中尝试了 Image(uiImage: animatedImage)

var images: [UIImage]! = [UIImage(named: "Sequence/frame-1")!,
                          UIImage(named: "Sequence/frame-2")!,
                          UIImage(named: "Sequence/frame-3")!,
                          UIImage(named: "Sequence/frame-4")!,
                          UIImage(named: "Sequence/frame-5")!,
                          UIImage(named: "Sequence/frame-6")!
]

let animatedImage : UIImage! = UIImage.animatedImage(with: images, duration: 1.0)

//////Then in the ContentView.swift I've tried this code:

struct ContentView : View {
    var body: some View {

        Image(uiImage: animatedImage)

    }
}

当我 运行 程序时它只显示第一帧,但我期望帧的动画

您尝试过制作自己的视图包装器吗?

像这样:

struct AnimatedImage: UIViewRepresentable {
    func makeUIView(context: Self.Context) -> UIImageView {
        return UIImageView(image: animatedImage)
    }

    func updateUIView(_ uiView: UIImageView, context: UIViewRepresentableContext<AnimatedImage>) {
    }
}

用法为:

struct ContentView : View {
    var body: some View {
        AnimatedImage()
    }
}

不确定这是否能解决您的问题,至少它不再是静态图像了:D

在 Xcode Beta 2 模拟器上试过了。

接受的答案非常有效,bhagyash ingale 提到的不幸问题使其很难使用。如果 Image 的特定方法可以通过协议或其他方式重用,那将会很有用。我有一个非常糟糕的,也许是巨大的大炮来解决这个问题,也许及时会更容易,但现在......

class LoadingTimer {

    let publisher = Timer.publish(every: 0.1, on: .main, in: .default)
    private var timerCancellable: Cancellable?

    func start() {
        self.timerCancellable = publisher.connect()
    }

    func cancel() {
        self.timerCancellable?.cancel()
    }
}
struct LoadingView: View {

    @State private var index = 0

    private let images = (0...7).map { UIImage(named: "Image-\([=11=]).jpg")! }
    private var timer = LoadingTimer()

    var body: some View {

        return Image(uiImage: images[index])
            .resizable()
            .frame(width: 100, height: 100, alignment: .center)
            .onReceive(
                timer.publisher,
                perform: { _ in
                    self.index = self.index + 1
                    if self.index >= 7 { self.index = 0 }
                }
            )
            .onAppear { self.timer.start() }
            .onDisappear { self.timer.cancel() }
    }
}

我不喜欢这个,但它完成了工作并依赖于 Image

没有 Combine 的版本

import SwiftUI

struct AnimatingImage: View {
    let images: [Image]

    @ObservedObject private var counter = Counter(interval: 0.05)
        
    var body: some View {
        images[counter.value % images.count]
    }
}

private class Counter: ObservableObject {
    private var timer: Timer?

    @Published var value: Int = 0
    
    init(interval: Double) {
        timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in self.value += 1 }
    }
}

可以用

struct LoadingView: View {
    private let images = (1...20).map { String(format: "i_%02d", [=11=]) }.map { Image([=11=]) }
    
    var body: some View {
        AnimatingImage(images: images)
    }
}

我想按顺序重复命名三个图像资产:thinking1、thinking2 和 thinking3。根据 nightwill 的回答,这适用于 iOS 14.4

// images[counter.value % images.count] mod ensures counter never exceeds images.count

import SwiftUI

struct ThinkingView: View {
    
    let images: [Image] = (1...3).map { String(format: "thinking%01d", [=10=]) }.map { Image([=10=]) }
    
    @ObservedObject private var counter = Counter(interval: 0.1)
    
    var body: some View {
        images[counter.value % images.count]
    }
}

private class Counter: ObservableObject {
    private var timer: Timer?

    @Published var value: Int = 0
    
    init(interval: Double) {
        timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in self.value += 1 }
    }
}

struct ThinkingView_Previews: PreviewProvider {
    static var previews: some View {
        ThinkingView()
    }
}

我从图像数组中找出了一个纯 SwftUI 动画: 它使用动画修改器来处理要显示的图像而不是视图(这里是带有图像的按钮)。当你点击一个按钮时,它的图像就会动画化。如果您再次点击图像停止动画和默认按钮图像 显示:

//
//  ImageArrayAnimationView
//
//  Created by Pitt Xav
//
import SwiftUI

struct ImageArrayAnimationView: View {
    // List of images
    private let images: [Image] = [
        Image(systemName: "icloud.and.arrow.up"),
        Image(systemName: "icloud.and.arrow.down"),
        Image(systemName: "cloud"),
        Image(systemName: "cloud.drizzle"),
        Image(systemName: "cloud.rain"),
        Image(systemName: "cloud.heavyrain")
    ]
    // Images and percent variable use for animating rain down
    private let imagesOrderDown = [2, 4, 5]
    @State var percentDown: Float = 0

    // Images and percent variable use for animating rain up
    @State var percentUp: Float = 0
    private let imagesOrderUp = [5, 3, 2]
    var body: some View {
        VStack {
            Spacer()
            HStack {
                Spacer()
                // tapping a button set percent to :
                // 0 = no animation (Animation.default)
                // 100 = animation with images
                Button {percentDown = percentDown < 50 ? 100 : 0} label: {
                    Image(systemName: "icloud.and.arrow.down")
                        .resizable()
                        .frame(width: 150, height: 150, alignment: .top)
                    .imageArrayAnimation(images: images, imagesOrder: imagesOrderDown, percent: percentDown)
                    .animation(percentDown <= 0 ? Animation.default : .linear(duration: 1).repeatForever(autoreverses: false), value: percentDown)
                    .border(.foreground, width: 2)
                    Spacer()
                }

                Button {percentUp = percentUp < 50 ? 100 : 0} label: {
                    Image(systemName: "icloud.and.arrow.up")
                        .resizable()
                        .frame(width: 150, height: 150, alignment: .top)
                    .imageArrayAnimation(images: images, imagesOrder: imagesOrderUp, percent: percentUp)
                    .animation(percentUp <= 0 ? Animation.default : .linear(duration: 1).repeatForever(autoreverses: false), value: percentUp)
                    .border(.foreground, width: 2)
                    Spacer()
                }
            }
            .frame(width: 100, height: 100, alignment: .center)
            Spacer()
        }
        
    }
}

struct ImageAnimation: AnimatableModifier {
    // Animation through th percent property
    // percent is converted into a position in an index array
    // this index value enable to choose whch image in the array
    // to display
    var animatableData: Float {
        get {
            percent
        }
        set {
            percent = newValue
        }
    }
    var images: [Image]
    var imgesOrder: [Int]
    var percent: Float
    var currentImageIndex: Int {
        if percent > 0 {
            let index = Int((percent / 100) *  Float(imgesOrder.count))
            print("% \(percent) \(index)")
            return imgesOrder[min(index, imgesOrder.count-1)]
        } else {
            return 0
        }
    }
    
    func body(content: Content) -> some View {
        ZStack(alignment: .top) {
            // for the animation to work content and image
            // needs to always be there
            // opacity enable to choose what to actually display
            content
                .opacity(percent == 0 ? 1 : 0)
            images[currentImageIndex]
                .resizable()
                .scaledToFit()
                .opacity(percent == 0 ? 0 : 1)
            
        }
    }
}

// extension to have mnamed modifier
extension View {
    func imageArrayAnimation(images: [Image], imagesOrder: [Int], percent: Float) -> some View {
        self.modifier(ImageAnimation(images: images, imgesOrder: imagesOrder, percent: percent))
    }
}

struct ImageArrayAnimationView_Previews: PreviewProvider {
    static var previews: some View {
        ImageArrayAnimationView()
    }
}