alexiscreuzot / SwiftyGif

High performance GIF engine
MIT License
2k stars 211 forks source link

SwiftyGif in SwiftUI #128

Closed ClaudiuStefaniga closed 1 year ago

ClaudiuStefaniga commented 4 years ago

Hello, I am trying to use SwiftyGif with SwiftUI but for some reasons the gif is resizing after I started even though I set a fixed frame. Here is my code:

` struct ContentView: View { @State var playGif = false

var body: some View {
    VStack {
        HStack {
            SwiftyGif(playGif: $playGif).frame(width: 300, height: 200)
            Spacer()
        }
        Spacer()
        Button(action: {
            self.playGif.toggle()
        }) {
            Text("toggle")
        }
    }
}

}

struct SwiftyGif: UIViewRepresentable { @Binding var playGif: Bool

func makeUIView(context: Context) -> UIImageView {
    let gif = try! UIImage(gifName: "bugs.gif")
    let imageview = UIImageView(gifImage: gif, loopCount: 3)
    return imageview
}

func updateUIView(_ gifImageView: UIImageView, context: Context) {
    if playGif == true {
        gifImageView.startAnimatingGif()
    } else {
        gifImageView.stopAnimatingGif()
    }
}

} `

guseulalio commented 3 years ago

Is there any update on this issue? I've been facing something similar.

yyl0 commented 3 years ago

@ClaudiuStefaniga @guseulalio

i had success setting imageview.frame = .zero in the UIViewRepresentable, and then setting the frame in the SwiftUI viewbuilder GIFView().frame(width: 50, height: 50)

elai950 commented 3 years ago

Same issue here, the gif took the whole screen

delvinwidjaja commented 3 years ago

Any update on this issue? I tried what @yyl0 suggested but it doesn't work...

crspybits commented 3 years ago

Similarly. I tried what @yyl0 suggested and it didn't work for me.

huylesmartdevices commented 2 years ago

Instead of using directly ImageView, we can use it as below:


        let view = UIView()
        let gif = try! UIImage(gifName: named)
        let imageView = UIImageView(gifImage: gif, loopCount: loopCount)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(imageView)
        NSLayoutConstraint.activate([
            imageView.heightAnchor.constraint(equalTo: view.heightAnchor),
            imageView.widthAnchor.constraint(equalTo: view.widthAnchor)
        ])
        return view
    }```
louislung commented 2 years ago

Following solution is working for me, Xcode 13.3, SwiftUI 5.6

import SwiftUI
import SwiftyGif

struct A9_SwiftyGif2: View {
    @State var playGif = false
    @State var gifName = "alternating_pogo"

    var body: some View {
        VStack {
            HStack {
                SwiftyGif(name: gifName, loopCount: 100, playGif: $playGif)
                    .frame(width: 100, height: 100)
                Spacer()

                SwiftyGif(name: "pokeball")
                    .frame(width: 100, height: 100)

            }
            .padding()
            Spacer()
            Button(action: {
                self.playGif.toggle()
            }) {
                Text("toggle")
            }
            Spacer()
        }
    }
}

struct A9_SwiftyGif2_Previews: PreviewProvider {
    static var previews: some View {
        A9_SwiftyGif2()
    }
}

class UIGIFImageView: UIView {
    private var image = UIImage()
    var imageView = UIImageView()
    private var data: Data?
    private var name: String?
    private var loopCount: Int?
    open var playGif: Bool?

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    convenience init(name: String, loopCount: Int, playGif: Bool) {
        self.init()
        self.name = name
        self.loopCount = loopCount
        self.playGif = playGif
        createGIF()
    }

    convenience init(data: Data, loopCount: Int, playGif: Bool) {
        self.init()
        self.data = data
        self.loopCount = loopCount
        self.playGif = playGif
        createGIF()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = bounds
        self.addSubview(imageView)
    }

    func createGIF() {
        do {
            if let data = data {
                image = try UIImage(gifData: data)
            } else {
                image = try UIImage(gifName: self.name!)
            }
        } catch {
            print(error)
        }

        imageView = UIImageView(gifImage: image, loopCount: loopCount!)
        // .scaleAspectFit keeps the aspect ratio of the gif
        imageView.contentMode = .scaleAspectFit
    }
}

struct SwiftyGif: UIViewRepresentable {
    private let data: Data?
    private let name: String?
    private let loopCount: Int?
    @Binding var playGif: Bool

    init(data: Data, loopCount: Int = -1, playGif: Binding<Bool> = .constant(true)) {
        self.data = data
        self.name = nil
        self.loopCount = loopCount
        self._playGif = playGif
    }

    init(name: String, loopCount: Int = -1, playGif: Binding<Bool> = .constant(true)) {
        self.data = nil
        self.name = name
        self.loopCount = loopCount
        self._playGif = playGif
    }

    func makeUIView(context: Context) -> UIGIFImageView {
        var gifImageView: UIGIFImageView
        if let data = data {
            gifImageView = UIGIFImageView(data: data, loopCount: loopCount!, playGif: playGif)
        } else {
            gifImageView = UIGIFImageView(name: name!, loopCount: loopCount!, playGif: playGif)
        }
        return gifImageView
    }

    func updateUIView(_ gifImageView: UIGIFImageView, context: Context) {
        if playGif {
            gifImageView.imageView.startAnimatingGif()
        } else {
            gifImageView.imageView.stopAnimatingGif()
        }
    }
}
louislung commented 2 years ago

I have come up with another modified version that allow user to switch the gif image

import SwiftUI
import SwiftyGif

struct A9_SwiftyGif_final: View {
    @State private var playGif = false
    @State private var gifName = "alternating_pogo"

    var body: some View {
        VStack {
            SwiftyGif(name: gifName, playGif: $playGif)
                // TODO: why cannot use width: .infinity
                .frame(width: 300)
            Spacer()
            Button {
                gifName = (gifName == "alternating_pogo") ? "lunge_jump":"alternating_pogo"
            } label: {
                Text("Change Gif")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(BorderedButtonStyle())

            Spacer()
            Button {
                playGif.toggle()
            } label: {
                Text("Stop/Start Play")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(BorderedButtonStyle())
            Spacer()
        }
    }
}

import Foundation
import UIKit
import SwiftUI

// Rendering GIFs in UIKIt

class UIGIFImageView: UIView {
    private var image = UIImage()
    var imageView = UIImageView()
    private var data: Data?
    private var name: String?
    private var loopCount: Int?
    private var playGif: Bool?

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    convenience init(name: String, loopCount: Int, playGif: Bool) {
        self.init()
        self.name = name
        self.loopCount = loopCount
        self.playGif = playGif
        self.layoutSubviews()
    }

    convenience init(data: Data, loopCount: Int, playGif: Bool) {
        self.init()
        self.data = data
        self.loopCount = loopCount
        self.playGif = playGif
        self.layoutSubviews()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = bounds
        self.addSubview(imageView)
    }

    func updateGIF(name: String, data: Data?, loopCount: Int) {
        do {
            if let data = data {
                image = try UIImage(gifData: data)
            } else {
                print(name)
                image = try UIImage(gifName: name)
            }
        } catch {
            print(error)
        }

        if let subview = self.subviews.first as? UIImageView {
            // if new gif, remove old to show new
            if image.imageData != subview.gifImage?.imageData {
                imageView = UIImageView(gifImage: image, loopCount: loopCount)
                imageView.contentMode = .scaleAspectFit
                subview.removeFromSuperview()
            }
        } else {
            print("error: no existing subview")
        }
    }
}

struct SwiftyGif: UIViewRepresentable {
    private let data: Data?
    private let name: String?
    private let loopCount: Int?
    @Binding var playGif: Bool

    init(data: Data, loopCount: Int = -1, playGif: Binding<Bool> = .constant(true)) {
        self.data = data
        self.name = nil
        self.loopCount = loopCount
        self._playGif = playGif
    }

    init(name: String, loopCount: Int = -1, playGif: Binding<Bool> = .constant(true)) {
        self.data = nil
        self.name = name
        self.loopCount = loopCount
        self._playGif = playGif
    }

    func makeUIView(context: Context) -> UIGIFImageView {
        var gifImageView: UIGIFImageView
        if let data = data {
            gifImageView = UIGIFImageView(data: data, loopCount: loopCount!, playGif: playGif)
        } else {
            gifImageView = UIGIFImageView(name: name!, loopCount: loopCount!, playGif: playGif)
        }
        return gifImageView
    }

    func updateUIView(_ gifImageView: UIGIFImageView, context: Context) {
        gifImageView.updateGIF(name: name ?? "", data: data, loopCount: loopCount!)

        if playGif {
            gifImageView.imageView.startAnimatingGif()
        } else {
            gifImageView.imageView.stopAnimatingGif()
        }
    }
}
TrabelsiAchraf commented 2 years ago

Thank your for the solution !

andylin2004 commented 1 year ago

macOS version based on @louislung's code:

class NSGIFImageView: NSView {
    private var image = NSImage()
    var imageView = NSImageView()
    private var data: Data?
    private var name: String?
    private var loopCount: Int?
    private var playGif: Bool?

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    convenience init(name: String, loopCount: Int, playGif: Bool) {
        self.init()
        self.name = name
        self.loopCount = loopCount
        self.playGif = playGif
        self.layout()
    }

    convenience init(data: Data, loopCount: Int, playGif: Bool) {
        self.init()
        self.data = data
        self.loopCount = loopCount
        self.playGif = playGif
        self.layout()
    }

    override func layout() {
        super.layout()
        imageView.frame = bounds
        self.addSubview(imageView)
    }

    func updateGIF(name: String, data: Data?, loopCount: Int) {
        do {
            if let data = data {
                image = try NSImage(gifData: data)
            } else {
                print(name)
                image = try NSImage(gifName: name)
            }
        } catch {
            print(error)
        }

        if let subview = self.subviews.first as? NSImageView {
            // if new gif, remove old to show new
            if image.imageData != subview.gifImage?.imageData {
                imageView = NSImageView(gifImage: image, loopCount: loopCount)
                imageView.imageScaling = .scaleProportionallyDown
                subview.removeFromSuperview()
            }
        } else {
            print("error: no existing subview")
        }
    }
}

struct SwiftyGif: NSViewRepresentable {
    typealias NSViewType = NSGIFImageView

    private let data: Data?
    private let name: String?
    private let loopCount: Int?
    @Binding var playGif: Bool

    init(url: URL) {
        self.data = try? Data(contentsOf: url)
        self.name = nil
        self.loopCount = -1
        self._playGif = .constant(true)
    }

    init(data: Data, loopCount: Int = -1, playGif: Binding<Bool> = .constant(true)) {
        self.data = data
        self.name = nil
        self.loopCount = loopCount
        self._playGif = playGif
    }

    init(name: String, loopCount: Int = -1, playGif: Binding<Bool> = .constant(true)) {
        self.data = nil
        self.name = name
        self.loopCount = loopCount
        self._playGif = playGif
    }

    func makeNSView(context: Context) -> NSGIFImageView {
        var gifImageView: NSGIFImageView
        if let data = data {
            gifImageView = NSGIFImageView(data: data, loopCount: loopCount!, playGif: playGif)
        } else {
            gifImageView = NSGIFImageView(name: name!, loopCount: loopCount!, playGif: playGif)
        }
        return gifImageView
    }

    func updateNSView(_ gifImageView: NSGIFImageView, context: Context) {
        gifImageView.updateGIF(name: name ?? "", data: data, loopCount: loopCount!)

        if playGif {
            gifImageView.imageView.startAnimatingGif()
        } else {
            gifImageView.imageView.stopAnimatingGif()
        }
    }
}
hkellaway commented 1 year ago

in case this helps another seeking SwiftUI usage.

i have a gif coming from a remote URL. the parent view has an ObservableObject that publishes that URL. here's my gif view:

struct AnimatedGifView: UIViewRepresentable {
    @Binding var url: URL

    func makeUIView(context: Context) -> UIImageView {
        let imageView = UIImageView(gifURL: self.url)
        imageView.contentMode = .scaleAspectFit
        return imageView
    }

    func updateUIView(_ uiView: UIImageView, context: Context) {
        uiView.setGifFromURL(self.url)
    }
}

and here's the call-site in the parent where viewModel is the ObservableObject:

AnimatedGifView(url: Binding(get: { self.viewModel.gif.url }, set: { _ in }))