I want to animate images in SwiftUI's Image view
First, I tried creating some variables and a function to toggle the Image("imageVariable"). It changes but there is no animation even tried the withAnimation { } method
Secondly, I tried to use a UIKit view. Here, the animation works but I can't apply the resizable() modifier or a set a fixed frame
var images: [UIImage]! = [UIImage(named: "pushup001")!, UIImage(named: "pushup002")!] let animatedImage = UIImage.animatedImage(with: images, duration: 0.5) struct workoutAnimation: UIViewRepresentable { func makeUIView(context: Self.Context) -> UIImageView { return UIImageView(image: animatedImage) } func updateUIView(_ uiView: UIImageView, context: UIViewRepresentableContext<workoutAnimation>) { } } struct WorkoutView: View { var body: some View { VStack { workoutAnimation().aspectRatio(contentMode: .fit) } } } In method 1 I can change the image but not animate, while, in method 2 I can animate but not control it's size
44 Answers
If you want a robust and cross-platform SwiftUI implementation for animated images, like GIF/APNG/WebP, I recommend using SDWebImageSwiftUI. This framework is based on exist success image loading framework SDWebImage and provides a SwiftUI binding.
To play the animation, use AnimatedImage view.
var body: some View { Group { // Network AnimatedImage(url: URL(string: "")) .onFailure(perform: { (error) in // Error }) } } I solved this using UIViewRepresentable protocol. Here I returned a UIView with the ImageView as it's subview. This gave me more control over the child's size, etc.
import SwiftUI var images : [UIImage]! = [UIImage(named: "pushup001")!, UIImage(named: "pushup002")!] let animatedImage = UIImage.animatedImage(with: images, duration: 0.5) struct workoutAnimation: UIViewRepresentable { func makeUIView(context: Self.Context) -> UIView { let someView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400)) let someImage = UIImageView(frame: CGRect(x: 20, y: 100, width: 360, height: 180)) someImage.clipsToBounds = true someImage.layer.cornerRadius = 20 someImage.autoresizesSubviews = true someImage.contentMode = UIView.ContentMode.scaleAspectFill someImage.image = animatedImage someView.addSubview(someImage) return someView } func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<workoutAnimation>) { } } struct WorkoutView: View { var body: some View { VStack (alignment: HorizontalAlignment.center, spacing: 10) { workoutAnimation() Text("zzzz") } } } 1I have created an image animation class that can be easily reused
import SwiftUI struct ImageAnimated: UIViewRepresentable { let imageSize: CGSize let imageNames: [String] let duration: Double = 0.5 func makeUIView(context: Self.Context) -> UIView { let containerView = UIView(frame: CGRect(x: 0, y: 0 , width: imageSize.width, height: imageSize.height)) let animationImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)) animationImageView.clipsToBounds = true animationImageView.layer.cornerRadius = 5 animationImageView.autoresizesSubviews = true animationImageView.contentMode = UIView.ContentMode.scaleAspectFill var images = [UIImage]() imageNames.forEach { imageName in if let img = UIImage(named: imageName) { images.append(img) } } animationImageView.image = UIImage.animatedImage(with: images, duration: duration) containerView.addSubview(animationImageView) return containerView } func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<ImageAnimated>) { } } The way to use it:
ImageAnimated(imageSize: CGSize(width: size, height: size), imageNames: ["loading1","loading2","loading3","loading4"], duration: 0.3) .frame(width: size, height: size, alignment: .center) 2in model :
var publisher : Timer? @Published var index = 0 func startTimer() { index = 0 publisher = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: {_ in if self.index < count/*count of frames*/{ self.index += 1 } else if let timer = self.publisher { timer.invalidate() self.publisher = nil } }) } } in view :
struct MyAnimationView : View { let width : CGFloat let images = (0...60).map { UIImage(named: "tile\($0)")! } @StateObject var viewmodel : MyViewModel var body: some View { Image(uiImage: images[viewmodel.index]) .resizable() .frame(width: width, height: width, alignment: .center) } }