Remove delegate
This commit is contained in:
parent
a885c995e9
commit
a2894d59cd
|
@ -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 */,
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue