Invalidate display link
This commit is contained in:
parent
a2894d59cd
commit
6a22c41aa1
|
@ -1,17 +1,15 @@
|
||||||
import ImageIO
|
|
||||||
import Runes
|
import Runes
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/// A subclass of `UIImageView` that can be animated using an image name string or raw data.
|
/// A subclass of `UIImageView` that can be animated using an image name string or raw data.
|
||||||
public class AnimatableImageView: UIImageView {
|
public class AnimatableImageView: UIImageView {
|
||||||
/// An `Animator` instance that holds the frames of a specific image in memory.
|
/// An `Animator` instance that holds the frames of a specific image in memory.
|
||||||
var animator: Animator?
|
private var animator: Animator?
|
||||||
/// A display link that keeps calling the `updateFrame` method on every screen refresh.
|
/// A display link that keeps calling the `updateFrame` method on every screen refresh.
|
||||||
private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: Selector("updateFrame"))
|
private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: Selector("updateFrame"))
|
||||||
|
|
||||||
deinit {
|
/// The size of the frame cache.
|
||||||
println("deinit animatable view")
|
public var framePreloadCount = 50
|
||||||
}
|
|
||||||
|
|
||||||
/// A computed property that returns whether the image view is animating.
|
/// A computed property that returns whether the image view is animating.
|
||||||
public var isAnimatingGIF: Bool {
|
public var isAnimatingGIF: Bool {
|
||||||
|
@ -32,7 +30,8 @@ public class AnimatableImageView: UIImageView {
|
||||||
/// :param: data GIF image data.
|
/// :param: data GIF image data.
|
||||||
public func prepareForAnimation(imageData data: NSData) {
|
public func prepareForAnimation(imageData data: NSData) {
|
||||||
image = UIImage(data: data)
|
image = UIImage(data: data)
|
||||||
animator = Animator(data: data, size: frame.size, contentMode: contentMode)
|
animator = Animator(data: data, size: frame.size, contentMode: contentMode, framePreloadCount: framePreloadCount)
|
||||||
|
animator?.prepareFrames()
|
||||||
attachDisplayLink()
|
attachDisplayLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,18 +73,16 @@ public class AnimatableImageView: UIImageView {
|
||||||
/// Stops the image view animation.
|
/// Stops the image view animation.
|
||||||
public func stopAnimatingGIF() {
|
public func stopAnimatingGIF() {
|
||||||
displayLink.paused = true
|
displayLink.paused = true
|
||||||
cleanup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cleanup the animator to reduce memory.
|
/// Invalidate the displayLink so it releases this object.
|
||||||
public func cleanup() {
|
public func cleanup() {
|
||||||
image = .None
|
displayLink.invalidate()
|
||||||
animator = .None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attaches the dsiplay link.
|
/// Attaches the display link.
|
||||||
func attachDisplayLink() {
|
private func attachDisplayLink() {
|
||||||
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
|
displayLink.addToRunLoop(.mainRunLoop(), forMode: NSRunLoopCommonModes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ImageIO
|
||||||
import Runes
|
import Runes
|
||||||
|
|
||||||
/// Responsible for storing and updating the frames of a `AnimatableImageView` instance via delegation.
|
/// Responsible for storing and updating the frames of a `AnimatableImageView` instance via delegation.
|
||||||
class Animator: NSObject {
|
class Animator {
|
||||||
/// Maximum duration to increment the frame timer with.
|
/// Maximum duration to increment the frame timer with.
|
||||||
private let maxTimeStep = 1.0
|
private let maxTimeStep = 1.0
|
||||||
/// An array of animated frames from a single GIF image.
|
/// An array of animated frames from a single GIF image.
|
||||||
|
@ -13,7 +13,7 @@ class Animator: NSObject {
|
||||||
/// The content mode to use when resizing
|
/// The content mode to use when resizing
|
||||||
private let contentMode: UIViewContentMode
|
private let contentMode: UIViewContentMode
|
||||||
/// Maximum number of frames to load at once
|
/// Maximum number of frames to load at once
|
||||||
private let maxNumberOfFrames = 50
|
private let maxNumberOfFrames: Int
|
||||||
/// The total number of frames in the GIF.
|
/// The total number of frames in the GIF.
|
||||||
private var numberOfFrames = 0
|
private var numberOfFrames = 0
|
||||||
/// A reference to the original image source.
|
/// A reference to the original image source.
|
||||||
|
@ -37,24 +37,19 @@ class Animator: NSObject {
|
||||||
///
|
///
|
||||||
/// :param: data The raw GIF image data.
|
/// :param: data The raw GIF image data.
|
||||||
/// :param: delegate An `Animatable` delegate.
|
/// :param: delegate An `Animatable` delegate.
|
||||||
required init(data: NSData, size: CGSize, contentMode: UIViewContentMode) {
|
init(data: NSData, size: CGSize, contentMode: UIViewContentMode, framePreloadCount: Int) {
|
||||||
let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse]
|
let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse]
|
||||||
imageSource = CGImageSourceCreateWithData(data, options)
|
imageSource = CGImageSourceCreateWithData(data, options)
|
||||||
self.size = size
|
self.size = size
|
||||||
self.contentMode = contentMode
|
self.contentMode = contentMode
|
||||||
super.init()
|
maxNumberOfFrames = framePreloadCount
|
||||||
prepareFrames()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
println("deinit animator")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Frames
|
// MARK: - Frames
|
||||||
/// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`.
|
/// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`.
|
||||||
private func prepareFrames() {
|
func prepareFrames() {
|
||||||
numberOfFrames = Int(CGImageSourceGetCount(imageSource))
|
numberOfFrames = Int(CGImageSourceGetCount(imageSource))
|
||||||
let framesToProcess = numberOfFrames > maxNumberOfFrames ? maxNumberOfFrames : numberOfFrames
|
let framesToProcess = min(numberOfFrames, maxNumberOfFrames)
|
||||||
animatedFrames.reserveCapacity(framesToProcess)
|
animatedFrames.reserveCapacity(framesToProcess)
|
||||||
animatedFrames = reduce(0..<framesToProcess, []) { $0 + pure(prepareFrame($1)) }
|
animatedFrames = reduce(0..<framesToProcess, []) { $0 + pure(prepareFrame($1)) }
|
||||||
}
|
}
|
||||||
|
@ -91,8 +86,6 @@ class Animator: NSObject {
|
||||||
///
|
///
|
||||||
/// :returns: An optional image at a given frame.
|
/// :returns: An optional image at a given frame.
|
||||||
func updateCurrentFrame(duration: CFTimeInterval) -> Bool {
|
func updateCurrentFrame(duration: CFTimeInterval) -> Bool {
|
||||||
if animatedFrames.count <= 1 { return false }
|
|
||||||
|
|
||||||
timeSinceLastFrameChange += min(maxTimeStep, duration)
|
timeSinceLastFrameChange += min(maxTimeStep, duration)
|
||||||
var frameDuration = animatedFrames[currentFrameIndex % animatedFrames.count].duration
|
var frameDuration = animatedFrames[currentFrameIndex % animatedFrames.count].duration
|
||||||
|
|
||||||
|
@ -101,7 +94,7 @@ class Animator: NSObject {
|
||||||
let lastFrameIndex = currentFrameIndex
|
let lastFrameIndex = currentFrameIndex
|
||||||
currentFrameIndex = ++currentFrameIndex % numberOfFrames
|
currentFrameIndex = ++currentFrameIndex % numberOfFrames
|
||||||
|
|
||||||
// load the next needed frame for progressive loading
|
// Loads the next needed frame for progressive loading
|
||||||
if animatedFrames.count < numberOfFrames {
|
if animatedFrames.count < numberOfFrames {
|
||||||
let nextFrameToLoad = (lastFrameIndex + animatedFrames.count) % numberOfFrames
|
let nextFrameToLoad = (lastFrameIndex + animatedFrames.count) % numberOfFrames
|
||||||
animatedFrames[lastFrameIndex % animatedFrames.count] = prepareFrame(nextFrameToLoad)
|
animatedFrames[lastFrameIndex % animatedFrames.count] = prepareFrame(nextFrameToLoad)
|
||||||
|
|
Loading…
Reference in New Issue