From a2894d59cd9e4e0a6a65bda58374cd051fc96dcd Mon Sep 17 00:00:00 2001 From: Tony DiPasquale Date: Thu, 4 Jun 2015 19:24:34 -0400 Subject: [PATCH] Remove delegate --- Gifu.xcodeproj/project.pbxproj | 4 --- Source/Animatable.swift | 7 ----- Source/AnimatableImageView.swift | 30 +++++++++++++++--- Source/Animator.swift | 53 +++++++++++--------------------- 4 files changed, 43 insertions(+), 51 deletions(-) delete mode 100644 Source/Animatable.swift diff --git a/Gifu.xcodeproj/project.pbxproj b/Gifu.xcodeproj/project.pbxproj index 2007e08..161a960 100644 --- a/Gifu.xcodeproj/project.pbxproj +++ b/Gifu.xcodeproj/project.pbxproj @@ -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 = ""; }; 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableImageView.swift; sourceTree = ""; }; - 005656F01A7042E9008A0ED1 /* Animatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animatable.swift; sourceTree = ""; }; 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 = ""; }; 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceHelpers.swift; sourceTree = ""; }; @@ -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 */, diff --git a/Source/Animatable.swift b/Source/Animatable.swift deleted file mode 100644 index 881fc96..0000000 --- a/Source/Animatable.swift +++ /dev/null @@ -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 } -} diff --git a/Source/AnimatableImageView.swift b/Source/AnimatableImageView.swift index 4f4f0d9..cd01581 100644 --- a/Source/AnimatableImageView.swift +++ b/Source/AnimatableImageView.swift @@ -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) + } } diff --git a/Source/Animator.swift b/Source/Animator.swift index 9d734b0..ae4f7cf 100644 --- a/Source/Animator.swift +++ b/Source/Animator.swift @@ -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 } }