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

Ryan
  • 4,799
  • 1
  • 29
  • 56
arthas
  • 680
  • 1
  • 8
  • 16
  • 1
    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`. –  Jul 22 '19 at 18:44
  • @dfd I will try to do most things in the UIView and then simply use that for display in SwiftUI. – arthas Jul 23 '19 at 07:28
  • @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 ? – arthas Jul 23 '19 at 07:28
  • oh, if that's the case, then you're probably better off with UIKit (for the time being at least). – kontiki Jul 23 '19 at 07:44

4 Answers4

9

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")
        }
    }
}
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
arthas
  • 680
  • 1
  • 8
  • 16
  • 1
    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. – agrippa Oct 27 '20 at 11:00
  • not work for watchOS, since UIImageView is not exist in SwiftUI – Bill Chan Mar 21 '23 at 18:11
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
        })
    }
}
Cesare
  • 9,139
  • 16
  • 78
  • 130
DreamPiggy
  • 79
  • 2
  • 5
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)
         
}
}
1

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)
Julio Bailon
  • 3,735
  • 2
  • 33
  • 34
  • 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... – Michael Feb 23 '21 at 16:25
  • I don't know why but ImageAnimated() doesn't work in a Tabview(). What am I doing wrong? – Michael Feb 23 '21 at 20:31