Invalidate display link

This commit is contained in:
Tony DiPasquale 2015-06-04 19:36:47 -04:00
parent a2894d59cd
commit 6a22c41aa1
2 changed files with 17 additions and 27 deletions

View File

@ -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)
} }
} }

View File

@ -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)