How to animate images in SwiftUI, to play a frame animation
Asked Answered
I

4

9

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

Iron answered 22/7, 2019 at 17:52 Comment(4)
Regards to attempt #2... couldn't you simply move both the UI animation and the contentMode into your UIImageView? At that point the UIViewRelatable could simply render the contents the full size of the View.Despairing
@dfd I will try to do most things in the UIView and then simply use that for display in SwiftUI.Iron
@kontiki By animating images I mean playing a gif/frame animation in the same view, not a view transition. At this point it seems not possible in SwiftUI, hence gave up on that. Do you think it can be done without UIKit ?Iron
oh, if that's the case, then you're probably better off with UIKit (for the time being at least).Subtreasury
I
12

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")
        }
    }
}
Iron answered 24/7, 2019 at 7:56 Comment(2)
This did it for me after a long time looking + researching. Thanks! I had to move images and animatedImage into the makeUIView function. Not ideal but it was not compiling otherwise for me.Eldreda
not work for watchOS, since UIImageView is not exist in SwiftUICowbell
H
6

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: "https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"))
        .onFailure(perform: { (error) in
            // Error
        })
    }
}
Heterogeneous answered 22/10, 2019 at 13:40 Comment(0)
S
2

I 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)
Supinate answered 3/4, 2020 at 14:17 Comment(2)
I've tried the image animated class and it works fine on a single view. if I implement it on a tabView the animation didn't start. If I close the iPhone and open it the animation start. Maybe I need some kind of .onAppear() but I don't know where should I put it in...Deglutition
I don't know why but ImageAnimated() doesn't work in a Tabview(). What am I doing wrong?Deglutition
T
2

in 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)
         
}
}
Tuchman answered 13/10, 2021 at 8:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.