Last active
July 29, 2018 01:55
-
-
Save kazuhiro4949/85065375f5f8ea96444eeb32f4891f1e to your computer and use it in GitHub Desktop.
One shot gif animation with UIImageView.images
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
import UIKit | |
import ImageIO | |
// make one shot gif animation from Gif. | |
let gif = Gif(with: "GIF File Name (not in asset catalog)")! | |
let base: Double = 4000 | |
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) | |
// set start image before starting animation | |
imageView.image = gif.frames.first | |
// set UIImageView animation | |
imageView.animationImages = gif.frames | |
imageView.animationDuration = gif.delays.reduce(0, +) / base | |
imageView.animationRepeatCount = 1 | |
// start animation | |
imageView.startAnimating() | |
// set end image after starting animation | |
imageView.image = gif.frames.last | |
/********** | |
Gif Class | |
***********/ | |
// convert gif data to UIImages and delays (duration). | |
// Modification from https://github.com/swiftgif/SwiftGif/blob/master/SwiftGifCommon/UIImage+Gif.swift | |
class Gif { | |
let frames: [UIImage] | |
let delays: [Double] | |
init?(with fileName: String) { | |
guard let url = Bundle.main.url(forResource: fileName, withExtension: ".gif"), | |
let data = try? Data(contentsOf: url), | |
let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { | |
return nil | |
} | |
let count = CGImageSourceGetCount(imageSource) | |
var _images = [CGImage]() | |
var _delays = [Int]() | |
(0..<count).forEach { (index) in | |
if let image = CGImageSourceCreateImageAtIndex(imageSource, index, nil) { | |
_images.append(image) | |
} | |
_delays.append(Int(Gif.delay(at: index, source: imageSource) * 1000)) | |
} | |
let gcd = Gif.calcGcd(for: _delays) | |
var _frames = [UIImage]() | |
(0..<count).forEach { (index) in | |
let frame = UIImage(cgImage: _images[index]) | |
let frameCount = _delays[index] / gcd | |
(0..<frameCount).forEach { _ in | |
_frames.append(frame) | |
} | |
} | |
self.frames = _frames | |
self.delays = _delays.map(Double.init) | |
} | |
static func calcGcd(for array: [Int]) -> Int { | |
guard !array.isEmpty else { | |
return 1 | |
} | |
return array.reduce(array[0]) { (gcd, elem) in | |
return calcGcd(elem, gcd) | |
} | |
} | |
// https://github.com/raywenderlich/swift-algorithm-club/tree/master/GCD#greatest-common-divisor | |
static func calcGcd(_ a: Int, _ b: Int) -> Int { | |
let r = a % b | |
if r != 0 { | |
return calcGcd(b, r) | |
} else { | |
return b | |
} | |
} | |
static func delay(at index: Int, source: CGImageSource) -> Double { | |
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) | |
let gifPropertyPointer = CFDictionaryGetValue( | |
cfProperties, | |
Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()) | |
let gifProperty = unsafeBitCast(gifPropertyPointer, to: CFDictionary.self) | |
let unclampedDelayTimeObjPointer = CFDictionaryGetValue( | |
gifProperty, | |
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()) | |
var delayTime = unsafeBitCast(unclampedDelayTimeObjPointer, to: Double.self) | |
if delayTime == 0 { | |
let delayTimeObjPointer = CFDictionaryGetValue(gifProperty, Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()) | |
delayTime = unsafeBitCast(delayTimeObjPointer, to: Double.self) | |
} | |
return max(delayTime, 0.1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment