Initial commit
This commit is contained in:
commit
25221506bd
|
@ -0,0 +1,34 @@
|
||||||
|
import UIKit
|
||||||
|
import ImageIO
|
||||||
|
import MobileCoreServices
|
||||||
|
|
||||||
|
func CGImageSourceContainsAnimatedGIF(imageSource: CGImageSource) -> Bool {
|
||||||
|
let isTypeGIF = UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)
|
||||||
|
let imageCount = CGImageSourceGetCount(imageSource)
|
||||||
|
return isTypeGIF != 0 && imageCount > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func CGImageSourceGIFFrameDuration(imageSource: CGImageSource, index: Int) -> NSTimeInterval {
|
||||||
|
let containsAnimatedGIF = CGImageSourceContainsAnimatedGIF(imageSource)
|
||||||
|
if !containsAnimatedGIF { return 0.0 }
|
||||||
|
|
||||||
|
var duration = 0.0
|
||||||
|
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, UInt(index), nil) as NSDictionary
|
||||||
|
let GIFProperties: NSDictionary? = imageProperties[kCGImagePropertyGIFDictionary] as? NSDictionary
|
||||||
|
|
||||||
|
if let properties = GIFProperties {
|
||||||
|
duration = properties[kCGImagePropertyGIFUnclampedDelayTime] as Double
|
||||||
|
|
||||||
|
if duration <= 0 {
|
||||||
|
duration = properties[kCGImagePropertyGIFDelayTime] as Double
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let threshold = 0.02 - Double(FLT_EPSILON)
|
||||||
|
|
||||||
|
if duration > 0 && duration < threshold {
|
||||||
|
duration = 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
return duration
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
import UIKit
|
||||||
|
import ImageIO
|
||||||
|
|
||||||
|
class AnimatedImage: UIImage {
|
||||||
|
// MARK: - Constants
|
||||||
|
let framesToPreload = 10
|
||||||
|
let maxTimeStep = 1.0
|
||||||
|
|
||||||
|
// MARK: - Public Properties
|
||||||
|
var delegate: UIImageView?
|
||||||
|
var frameDurations = [NSTimeInterval]()
|
||||||
|
var frames = [UIImage?]()
|
||||||
|
var totalDuration: NSTimeInterval = 0.0
|
||||||
|
|
||||||
|
// MARK: - Private Properties
|
||||||
|
private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: "updateCurrentFrame")
|
||||||
|
private lazy var preloadFrameQueue: dispatch_queue_t = dispatch_queue_create("co.kaishin.GIFPreloadImages", DISPATCH_QUEUE_SERIAL)
|
||||||
|
private var currentFrameIndex = 0
|
||||||
|
private var imageSource: CGImageSource?
|
||||||
|
private var timeSinceLastFrameChange: NSTimeInterval = 0.0
|
||||||
|
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
var currentFrame: UIImage? {
|
||||||
|
return frameAtIndex(currentFrameIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isAnimated: Bool {
|
||||||
|
return imageSource != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Initializers
|
||||||
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(data: NSData, delegate: UIImageView?) {
|
||||||
|
let imageSource = CGImageSourceCreateWithData(data, nil)
|
||||||
|
self.delegate = delegate
|
||||||
|
|
||||||
|
if CGImageSourceContainsAnimatedGIF(imageSource) {
|
||||||
|
super.init()
|
||||||
|
attachDisplayLink()
|
||||||
|
prepareFrames(imageSource)
|
||||||
|
startAnimating()
|
||||||
|
} else {
|
||||||
|
super.init(data: data)
|
||||||
|
stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Factories
|
||||||
|
class func imageWithName(name: String, delegate: UIImageView?) -> Self? {
|
||||||
|
let path = NSBundle.mainBundle().bundlePath.stringByAppendingPathComponent(name)
|
||||||
|
let data = NSData.dataWithContentsOfFile(path, options: nil, error: nil)
|
||||||
|
return (data != nil) ? imageWithData(data, delegate: delegate) : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
class func imageWithData(data: NSData, delegate: UIImageView?) -> Self? {
|
||||||
|
return self(data: data, delegate: delegate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Display Link Helpers
|
||||||
|
func attachDisplayLink() {
|
||||||
|
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Frame Methods
|
||||||
|
private func prepareFrames(source: CGImageSource!) {
|
||||||
|
imageSource = source
|
||||||
|
|
||||||
|
let numberOfFrames = CGImageSourceGetCount(self.imageSource)
|
||||||
|
frameDurations.reserveCapacity(Int(numberOfFrames))
|
||||||
|
frames.reserveCapacity(Int(numberOfFrames))
|
||||||
|
|
||||||
|
for index in 0..<Int(numberOfFrames) {
|
||||||
|
let frameDuration = CGImageSourceGIFFrameDuration(source, index)
|
||||||
|
frameDurations.append(frameDuration)
|
||||||
|
totalDuration += frameDuration
|
||||||
|
|
||||||
|
if index < framesToPreload {
|
||||||
|
let frameImageRef = CGImageSourceCreateImageAtIndex(self.imageSource, UInt(index), nil)
|
||||||
|
let frame = UIImage(CGImage: frameImageRef, scale: 0.0, orientation: UIImageOrientation.Up)
|
||||||
|
frames.append(frame)
|
||||||
|
} else {
|
||||||
|
frames.append(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func frameAtIndex(index: Int) -> UIImage? {
|
||||||
|
if Int(index) >= self.frames.count { return nil }
|
||||||
|
|
||||||
|
var image: UIImage? = self.frames[Int(index)]
|
||||||
|
|
||||||
|
updatePreloadedFramesAtIndex(index)
|
||||||
|
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatePreloadedFramesAtIndex(index: Int) {
|
||||||
|
if frames.count <= framesToPreload { return }
|
||||||
|
|
||||||
|
if index != 0 {
|
||||||
|
frames[index] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for internalIndex in (index + 1)...(index + framesToPreload) {
|
||||||
|
let adjustedIndex = internalIndex % frames.count
|
||||||
|
|
||||||
|
if frames[adjustedIndex] == nil {
|
||||||
|
dispatch_async(preloadFrameQueue) {
|
||||||
|
let frameImageRef = CGImageSourceCreateImageAtIndex(self.imageSource, UInt(adjustedIndex), nil)
|
||||||
|
self.frames[adjustedIndex] = UIImage(CGImage: frameImageRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCurrentFrame() {
|
||||||
|
if !isAnimated { return }
|
||||||
|
|
||||||
|
timeSinceLastFrameChange += min(maxTimeStep, displayLink.duration)
|
||||||
|
var frameDuration = frameDurations[currentFrameIndex]
|
||||||
|
|
||||||
|
while timeSinceLastFrameChange >= frameDuration {
|
||||||
|
timeSinceLastFrameChange -= frameDuration
|
||||||
|
currentFrameIndex++
|
||||||
|
|
||||||
|
if currentFrameIndex >= frames.count {
|
||||||
|
currentFrameIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate?.layer.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Animation
|
||||||
|
func stopAnimating() {
|
||||||
|
displayLink.paused = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func startAnimating() {
|
||||||
|
displayLink.paused = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAnimating() -> Bool {
|
||||||
|
return !displayLink.paused
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIImageView {
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
var animatableImage: AnimatedImage? {
|
||||||
|
if image is AnimatedImage {
|
||||||
|
return image as? AnimatedImage
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isAnimating: Bool {
|
||||||
|
return animatableImage?.isAnimating() ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
var animatable: Bool {
|
||||||
|
return animatableImage != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Method Overrides
|
||||||
|
override public func displayLayer(layer: CALayer!) {
|
||||||
|
if let image = animatableImage {
|
||||||
|
if let frame = image.currentFrame {
|
||||||
|
layer.contents = frame.CGImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Setter Functions
|
||||||
|
func setAnimatedImage(named name: String) {
|
||||||
|
image = AnimatedImage.imageWithName(name, delegate: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAnimatedImage(#data: NSData) {
|
||||||
|
image = AnimatedImage.imageWithData(data, delegate: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Animation
|
||||||
|
func startAnimating() {
|
||||||
|
if animatable {
|
||||||
|
animatableImage!.startAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopAnimating() {
|
||||||
|
if animatable {
|
||||||
|
animatableImage!.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue