Remove delegate
This commit is contained in:
parent
a885c995e9
commit
a2894d59cd
|
@ -9,7 +9,6 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656EC1A6F14D6008A0ED1 /* Animator.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 */; };
|
||||
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 */; };
|
||||
|
@ -21,7 +20,6 @@
|
|||
/* Begin PBXFileReference section */
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
|
@ -77,7 +75,6 @@
|
|||
005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */,
|
||||
EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */,
|
||||
005656EC1A6F14D6008A0ED1 /* Animator.swift */,
|
||||
005656F01A7042E9008A0ED1 /* Animatable.swift */,
|
||||
00B8C7951A3650EE00C188E7 /* Gifu.h */,
|
||||
00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */,
|
||||
EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */,
|
||||
|
@ -175,7 +172,6 @@
|
|||
005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */,
|
||||
005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */,
|
||||
EAF49CB11A3B6EEB00B395DF /* AnimatedFrame.swift in Sources */,
|
||||
005656F11A7042E9008A0ED1 /* Animatable.swift in Sources */,
|
||||
00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */,
|
||||
EAF49C811A3A4FAA00B395DF /* Curry.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
|
||||
|
||||
/// 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.
|
||||
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 {
|
||||
println("deinit animatable view")
|
||||
|
@ -13,7 +15,7 @@ public class AnimatableImageView: UIImageView, Animatable {
|
|||
|
||||
/// A computed property that returns whether the image view is animating.
|
||||
public var isAnimatingGIF: Bool {
|
||||
return animator?.isAnimating ?? isAnimating()
|
||||
return !displayLink.paused
|
||||
}
|
||||
|
||||
/// 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.
|
||||
public func prepareForAnimation(imageData data: NSData) {
|
||||
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.
|
||||
|
@ -54,18 +57,35 @@ public class AnimatableImageView: UIImageView, Animatable {
|
|||
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.
|
||||
public func startAnimatingGIF() {
|
||||
animator?.resumeAnimation() ?? startAnimating()
|
||||
if animator?.isAnimatable ?? false {
|
||||
displayLink.paused = false
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the image view animation.
|
||||
public func stopAnimatingGIF() {
|
||||
animator?.pauseAnimation() ?? stopAnimating()
|
||||
displayLink.paused = true
|
||||
cleanup()
|
||||
}
|
||||
|
||||
/// Cleanup the animator to reduce memory.
|
||||
public func cleanup() {
|
||||
image = .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.
|
||||
class Animator: NSObject {
|
||||
/// The animator delegate. Should conform to the `Animatable` protocol.
|
||||
let delegate: Animatable
|
||||
/// Maximum duration to increment the frame timer with.
|
||||
private let maxTimeStep = 1.0
|
||||
/// An array of animated frames from a single GIF image.
|
||||
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
|
||||
private let maxNumberOfFrames = 50
|
||||
/// The total number of frames in the GIF.
|
||||
|
@ -20,30 +22,28 @@ class Animator: NSObject {
|
|||
private var currentFrameIndex = 0
|
||||
/// Time elapsed since the last frame change. Used to determine when the frame should be updated.
|
||||
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.
|
||||
var currentFrame: UIImage? {
|
||||
return frameAtIndex(currentFrameIndex)
|
||||
}
|
||||
|
||||
/// Returns whether the animator is animating.
|
||||
var isAnimating: Bool {
|
||||
return !displayLink.paused
|
||||
/// Is this image animatable?
|
||||
var isAnimatable: Bool {
|
||||
return imageSource.isAnimatedGIF
|
||||
}
|
||||
|
||||
/// Initializes an animator instance from raw GIF image data and an `Animatable` delegate.
|
||||
///
|
||||
/// :param: data The raw GIF image data.
|
||||
/// :param: delegate An `Animatable` delegate.
|
||||
required init(data: NSData, delegate: Animatable) {
|
||||
imageSource = CGImageSourceCreateWithData(data, nil)
|
||||
self.delegate = delegate
|
||||
required init(data: NSData, size: CGSize, contentMode: UIViewContentMode) {
|
||||
let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse]
|
||||
imageSource = CGImageSourceCreateWithData(data, options)
|
||||
self.size = size
|
||||
self.contentMode = contentMode
|
||||
super.init()
|
||||
attachDisplayLink()
|
||||
prepareFrames()
|
||||
pauseAnimation()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -66,12 +66,11 @@ class Animator: NSObject {
|
|||
private func prepareFrame(index: Int) -> AnimatedFrame {
|
||||
let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index)
|
||||
let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil)
|
||||
let size = delegate.frame.size
|
||||
|
||||
let image = UIImage(CGImage: frameImageRef)
|
||||
let scaledImage: UIImage?
|
||||
|
||||
switch delegate.contentMode {
|
||||
switch contentMode {
|
||||
case .ScaleAspectFit: scaledImage = image?.resizeAspectFit(size)
|
||||
case .ScaleAspectFill: scaledImage = image?.resizeAspectFill(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`.
|
||||
///
|
||||
/// :returns: An optional image at a given frame.
|
||||
func updateCurrentFrame() {
|
||||
if animatedFrames.count <= 1 { return }
|
||||
func updateCurrentFrame(duration: CFTimeInterval) -> Bool {
|
||||
if animatedFrames.count <= 1 { return false }
|
||||
|
||||
timeSinceLastFrameChange += min(maxTimeStep, displayLink.duration)
|
||||
timeSinceLastFrameChange += min(maxTimeStep, duration)
|
||||
var frameDuration = animatedFrames[currentFrameIndex % animatedFrames.count].duration
|
||||
|
||||
if timeSinceLastFrameChange >= frameDuration {
|
||||
timeSinceLastFrameChange -= frameDuration
|
||||
let lastFrameIndex = currentFrameIndex
|
||||
currentFrameIndex = ++currentFrameIndex % numberOfFrames
|
||||
delegate.layer.setNeedsDisplay()
|
||||
|
||||
// load the next needed frame for progressive loading
|
||||
if animatedFrames.count < numberOfFrames {
|
||||
let nextFrameToLoad = (lastFrameIndex + animatedFrames.count) % numberOfFrames
|
||||
animatedFrames[lastFrameIndex % animatedFrames.count] = prepareFrame(nextFrameToLoad)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation
|
||||
/// 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)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue