Remove delegate

This commit is contained in:
Tony DiPasquale 2015-06-04 19:24:34 -04:00
parent a885c995e9
commit a2894d59cd
4 changed files with 43 additions and 51 deletions

View File

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656EC1A6F14D6008A0ED1 /* Animator.swift */; }; 005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656EC1A6F14D6008A0ED1 /* Animator.swift */; };
005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */; }; 005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */; };
005656F11A7042E9008A0ED1 /* Animatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656F01A7042E9008A0ED1 /* Animatable.swift */; };
00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */; }; 00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */; };
00B8C7961A3650EE00C188E7 /* Gifu.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B8C7951A3650EE00C188E7 /* Gifu.h */; settings = {ATTRIBUTES = (Public, ); }; }; 00B8C7961A3650EE00C188E7 /* Gifu.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B8C7951A3650EE00C188E7 /* Gifu.h */; settings = {ATTRIBUTES = (Public, ); }; };
EA1E21131AD5D369000459BD /* Runes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA1E21121AD5D369000459BD /* Runes.framework */; }; EA1E21131AD5D369000459BD /* Runes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA1E21121AD5D369000459BD /* Runes.framework */; };
@ -21,7 +20,6 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
005656EC1A6F14D6008A0ED1 /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = "<group>"; }; 005656EC1A6F14D6008A0ED1 /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = "<group>"; };
005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableImageView.swift; sourceTree = "<group>"; }; 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableImageView.swift; sourceTree = "<group>"; };
005656F01A7042E9008A0ED1 /* Animatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animatable.swift; sourceTree = "<group>"; };
00B8C73E1A364DA400C188E7 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 00B8C73E1A364DA400C188E7 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; };
00B8C7421A364DA400C188E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Source/Info.plist; sourceTree = "<group>"; }; 00B8C7421A364DA400C188E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Source/Info.plist; sourceTree = "<group>"; };
00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceHelpers.swift; sourceTree = "<group>"; }; 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceHelpers.swift; sourceTree = "<group>"; };
@ -77,7 +75,6 @@
005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */, 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */,
EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */, EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */,
005656EC1A6F14D6008A0ED1 /* Animator.swift */, 005656EC1A6F14D6008A0ED1 /* Animator.swift */,
005656F01A7042E9008A0ED1 /* Animatable.swift */,
00B8C7951A3650EE00C188E7 /* Gifu.h */, 00B8C7951A3650EE00C188E7 /* Gifu.h */,
00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */, 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */,
EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */, EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */,
@ -175,7 +172,6 @@
005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */, 005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */,
005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */, 005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */,
EAF49CB11A3B6EEB00B395DF /* AnimatedFrame.swift in Sources */, EAF49CB11A3B6EEB00B395DF /* AnimatedFrame.swift in Sources */,
005656F11A7042E9008A0ED1 /* Animatable.swift in Sources */,
00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */, 00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */,
EAF49C811A3A4FAA00B395DF /* Curry.swift in Sources */, EAF49C811A3A4FAA00B395DF /* Curry.swift in Sources */,
EAF49C7F1A3A4DE000B395DF /* UIImageExtension.swift in Sources */, EAF49C7F1A3A4DE000B395DF /* UIImageExtension.swift in Sources */,

View File

@ -1,7 +0,0 @@
/// Protocol that requires its members to have a `layer`, `frame`, and `contentMode` property.
/// Classes confirming to this protocol can serve as a delegate to `Animator`.
protocol Animatable {
var layer: CALayer { get }
var frame: CGRect { get }
var contentMode: UIViewContentMode { get }
}

View File

@ -3,9 +3,11 @@ 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, Animatable { 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? var animator: Animator?
/// A display link that keeps calling the `updateFrame` method on every screen refresh.
private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: Selector("updateFrame"))
deinit { deinit {
println("deinit animatable view") println("deinit animatable view")
@ -13,7 +15,7 @@ public class AnimatableImageView: UIImageView, Animatable {
/// 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 {
return animator?.isAnimating ?? isAnimating() return !displayLink.paused
} }
/// Prepares the frames using a GIF image file name, without starting the animation. /// Prepares the frames using a GIF image file name, without starting the animation.
@ -30,7 +32,8 @@ public class AnimatableImageView: UIImageView, Animatable {
/// :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, delegate: self) animator = Animator(data: data, size: frame.size, contentMode: contentMode)
attachDisplayLink()
} }
/// Prepares the frames using a GIF image file name and starts animating the image view. /// Prepares the frames using a GIF image file name and starts animating the image view.
@ -54,18 +57,35 @@ public class AnimatableImageView: UIImageView, Animatable {
image = animator?.currentFrame image = animator?.currentFrame
} }
/// Update the current frame with the displayLink duration
func updateFrame() {
if animator?.updateCurrentFrame(displayLink.duration) ?? false {
layer.setNeedsDisplay()
}
}
/// Starts the image view animation. /// Starts the image view animation.
public func startAnimatingGIF() { public func startAnimatingGIF() {
animator?.resumeAnimation() ?? startAnimating() if animator?.isAnimatable ?? false {
displayLink.paused = false
}
} }
/// Stops the image view animation. /// Stops the image view animation.
public func stopAnimatingGIF() { public func stopAnimatingGIF() {
animator?.pauseAnimation() ?? stopAnimating() displayLink.paused = true
cleanup()
} }
/// Cleanup the animator to reduce memory.
public func cleanup() { public func cleanup() {
image = .None
animator = .None animator = .None
} }
/// Attaches the dsiplay link.
func attachDisplayLink() {
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
} }

View File

@ -4,12 +4,14 @@ 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: NSObject {
/// The animator delegate. Should conform to the `Animatable` protocol.
let delegate: Animatable
/// 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.
private var animatedFrames = [AnimatedFrame]() private var animatedFrames = [AnimatedFrame]()
/// The size to resize all frames to
private let size: CGSize
/// The content mode to use when resizing
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 = 50
/// The total number of frames in the GIF. /// The total number of frames in the GIF.
@ -20,30 +22,28 @@ class Animator: NSObject {
private var currentFrameIndex = 0 private var currentFrameIndex = 0
/// Time elapsed since the last frame change. Used to determine when the frame should be updated. /// Time elapsed since the last frame change. Used to determine when the frame should be updated.
private var timeSinceLastFrameChange: NSTimeInterval = 0.0 private var timeSinceLastFrameChange: NSTimeInterval = 0.0
/// A display link that keeps calling the `updateCurrentFrame` method on every screen refresh.
private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: "updateCurrentFrame")
/// The current image frame to show. /// The current image frame to show.
var currentFrame: UIImage? { var currentFrame: UIImage? {
return frameAtIndex(currentFrameIndex) return frameAtIndex(currentFrameIndex)
} }
/// Returns whether the animator is animating. /// Is this image animatable?
var isAnimating: Bool { var isAnimatable: Bool {
return !displayLink.paused return imageSource.isAnimatedGIF
} }
/// Initializes an animator instance from raw GIF image data and an `Animatable` delegate. /// Initializes an animator instance from raw GIF image data and an `Animatable` delegate.
/// ///
/// :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, delegate: Animatable) { required init(data: NSData, size: CGSize, contentMode: UIViewContentMode) {
imageSource = CGImageSourceCreateWithData(data, nil) let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse]
self.delegate = delegate imageSource = CGImageSourceCreateWithData(data, options)
self.size = size
self.contentMode = contentMode
super.init() super.init()
attachDisplayLink()
prepareFrames() prepareFrames()
pauseAnimation()
} }
deinit { deinit {
@ -66,12 +66,11 @@ class Animator: NSObject {
private func prepareFrame(index: Int) -> AnimatedFrame { private func prepareFrame(index: Int) -> AnimatedFrame {
let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index) let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index)
let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil)
let size = delegate.frame.size
let image = UIImage(CGImage: frameImageRef) let image = UIImage(CGImage: frameImageRef)
let scaledImage: UIImage? let scaledImage: UIImage?
switch delegate.contentMode { switch contentMode {
case .ScaleAspectFit: scaledImage = image?.resizeAspectFit(size) case .ScaleAspectFit: scaledImage = image?.resizeAspectFit(size)
case .ScaleAspectFill: scaledImage = image?.resizeAspectFill(size) case .ScaleAspectFill: scaledImage = image?.resizeAspectFill(size)
default: scaledImage = image?.resize(size) default: scaledImage = image?.resize(size)
@ -91,41 +90,25 @@ class Animator: NSObject {
/// Updates the current frame if necessary using the frame timer and the duration of each frame in `animatedFrames`. /// Updates the current frame if necessary using the frame timer and the duration of each frame in `animatedFrames`.
/// ///
/// :returns: An optional image at a given frame. /// :returns: An optional image at a given frame.
func updateCurrentFrame() { func updateCurrentFrame(duration: CFTimeInterval) -> Bool {
if animatedFrames.count <= 1 { return } if animatedFrames.count <= 1 { return false }
timeSinceLastFrameChange += min(maxTimeStep, displayLink.duration) timeSinceLastFrameChange += min(maxTimeStep, duration)
var frameDuration = animatedFrames[currentFrameIndex % animatedFrames.count].duration var frameDuration = animatedFrames[currentFrameIndex % animatedFrames.count].duration
if timeSinceLastFrameChange >= frameDuration { if timeSinceLastFrameChange >= frameDuration {
timeSinceLastFrameChange -= frameDuration timeSinceLastFrameChange -= frameDuration
let lastFrameIndex = currentFrameIndex let lastFrameIndex = currentFrameIndex
currentFrameIndex = ++currentFrameIndex % numberOfFrames currentFrameIndex = ++currentFrameIndex % numberOfFrames
delegate.layer.setNeedsDisplay()
// load the next needed frame for progressive loading // load 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)
} }
} return true
} }
// MARK: - Animation return false
/// Pauses the display link.
func pauseAnimation() {
displayLink.paused = true
}
/// Resumes the display link.
func resumeAnimation() {
if animatedFrames.count > 1 {
displayLink.paused = false
}
}
/// Attaches the dsiplay link.
func attachDisplayLink() {
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
} }
} }