Last active
October 3, 2021 23:51
-
-
Save dheerajn/ed467955e63afb0ada70a5bb1c3ed7d3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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