Skip to content

Instantly share code, notes, and snippets.

@dheerajn
Last active October 3, 2021 23:51
Show Gist options
  • Save dheerajn/ed467955e63afb0ada70a5bb1c3ed7d3 to your computer and use it in GitHub Desktop.
Save dheerajn/ed467955e63afb0ada70a5bb1c3ed7d3 to your computer and use it in GitHub Desktop.
extension UIImage {
class func gifImage(data: Data) -> UIImage? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { return nil }
let count = CGImageSourceGetCount(source)
let delays = (0..<count).map {
Int(delayForImage(at: $0, source: source) * 1000)
}
let duration = delays.reduce(0, +)
let gcd = delays.reduce(0, gcd)
var frames = [UIImage]()
for i in 0..<count {
if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
let frame = UIImage(cgImage: cgImage)
let frameCount = delays[i] / gcd
for _ in 0..<frameCount {
frames.append(frame)
}
} else {
return nil
}
}
return UIImage.animatedImage(with: frames, duration: Double(duration) / 1000.0)
}
}
private func gcd(_ a: Int, _ b: Int) -> Int {
let absB = abs(b)
let r = abs(a) % absB
if r != 0 {
return gcd(absB, r)
} else {
return absB
}
}
private func delayForImage(at index: Int, source: CGImageSource) -> Double {
let defaultDelay = 1.0
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifPropertiesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 0)
defer {
gifPropertiesPointer.deallocate()
}
let unsafePointer = Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()
if CFDictionaryGetValueIfPresent(cfProperties, unsafePointer, gifPropertiesPointer) == false {
return defaultDelay
}
let gifProperties = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self)
var delayWrapper = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()), to: AnyObject.self)
if delayWrapper.doubleValue == 0 {
delayWrapper = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()),
to: AnyObject.self)
}
if let delay = delayWrapper as? Double, delay > 0 {
return delay
} else {
return defaultDelay
}
}
extension UIImage {
class func gifImage(name: String) -> UIImage? {
guard let url = Bundle.main.url(forResource: name, withExtension: "gif"),
let data = try? Data(contentsOf: url)
else {
return nil
}
return gifImage(data: data)
}
}
class UIGIFImage: UIView {
private let imageView = UIImageView()
private var name: String?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
convenience init(name: String) {
self.init()
self.name = name
initView()
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = bounds
self.addSubview(imageView)
}
func updateGIF(name: String) {
imageView.image = UIImage.gifImage(name: name)
}
private func initView() {
imageView.contentMode = .scaleAspectFit
}
}
struct GIFImage: UIViewRepresentable {
private let data: Data?
private let name: String?
init(data: Data) {
self.data = data
self.name = nil
}
public init(name: String) {
self.data = nil
self.name = name
}
func makeUIView(context: Context) -> UIGIFImage {
return UIGIFImage(name: name ?? "")
}
func updateUIView(_ uiView: UIGIFImage, context: Context) {
uiView.updateGIF(name: name ?? "")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment