diff --git a/Demo/demo/Main.storyboard b/Demo/demo/Main.storyboard index 93307c3..761ff6f 100755 --- a/Demo/demo/Main.storyboard +++ b/Demo/demo/Main.storyboard @@ -1,8 +1,7 @@ - + - - + @@ -17,13 +16,27 @@ - + + + - - - - - - - + diff --git a/Demo/demo/classes/ViewController.swift b/Demo/demo/classes/ViewController.swift index aa1d5d0..ce68108 100755 --- a/Demo/demo/classes/ViewController.swift +++ b/Demo/demo/classes/ViewController.swift @@ -3,17 +3,14 @@ import Gifu class ViewController: UIViewController { - @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var imageView: AnimatableImageView! @IBOutlet weak var button: FlatButton! override func viewDidLoad() { super.viewDidLoad() - if let image = AnimatedImage.animatedImageWithName("mugen.gif") { - imageView.setAnimatedImage(image) - imageView.startAnimatingGIF() - } - + imageView.animateWithImage(named: "mugen.gif") + UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: false) } diff --git a/Gifu.xcodeproj/project.pbxproj b/Gifu.xcodeproj/project.pbxproj index 71bd76a..9865f42 100644 --- a/Gifu.xcodeproj/project.pbxproj +++ b/Gifu.xcodeproj/project.pbxproj @@ -7,10 +7,11 @@ objects = { /* 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 */; }; 006F97011A6EDE7900CB5CE8 /* Runes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 006F97001A6EDE7900CB5CE8 /* Runes.framework */; }; - 00B8C75E1A364DCE00C188E7 /* AnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B8C75B1A364DCE00C188E7 /* AnimatedImage.swift */; }; 00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */; }; - 00B8C7601A364DCE00C188E7 /* UIImageView+Gifu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B8C75D1A364DCE00C188E7 /* UIImageView+Gifu.swift */; }; 00B8C7961A3650EE00C188E7 /* Gifu.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B8C7951A3650EE00C188E7 /* Gifu.h */; settings = {ATTRIBUTES = (Public, ); }; }; EAF49C7F1A3A4DE000B395DF /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */; }; EAF49C811A3A4FAA00B395DF /* Curry.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF49C801A3A4FAA00B395DF /* Curry.swift */; }; @@ -18,12 +19,13 @@ /* End PBXBuildFile section */ /* 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 = ""; }; 006F97001A6EDE7900CB5CE8 /* Runes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Runes.framework; path = "../Carthage/Checkouts/runes/build/Debug-iphoneos/Runes.framework"; 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 = ""; }; - 00B8C75B1A364DCE00C188E7 /* AnimatedImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatedImage.swift; sourceTree = ""; }; 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceHelpers.swift; sourceTree = ""; }; - 00B8C75D1A364DCE00C188E7 /* UIImageView+Gifu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Gifu.swift"; sourceTree = ""; }; 00B8C7951A3650EE00C188E7 /* Gifu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Gifu.h; sourceTree = ""; }; EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = ""; }; EAF49C801A3A4FAA00B395DF /* Curry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Curry.swift; sourceTree = ""; }; @@ -72,13 +74,14 @@ 00B8C75A1A364DBE00C188E7 /* Source */ = { isa = PBXGroup; children = ( + 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */, + EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */, + 005656EC1A6F14D6008A0ED1 /* Animator.swift */, + 005656F01A7042E9008A0ED1 /* Animatable.swift */, 00B8C7951A3650EE00C188E7 /* Gifu.h */, - 00B8C75B1A364DCE00C188E7 /* AnimatedImage.swift */, 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */, - 00B8C75D1A364DCE00C188E7 /* UIImageView+Gifu.swift */, EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */, EAF49C801A3A4FAA00B395DF /* Curry.swift */, - EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */, ); path = Source; sourceTree = ""; @@ -161,11 +164,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */, + 005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */, EAF49CB11A3B6EEB00B395DF /* AnimatedFrame.swift in Sources */, - 00B8C7601A364DCE00C188E7 /* UIImageView+Gifu.swift in Sources */, + 005656F11A7042E9008A0ED1 /* Animatable.swift in Sources */, 00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */, EAF49C811A3A4FAA00B395DF /* Curry.swift in Sources */, - 00B8C75E1A364DCE00C188E7 /* AnimatedImage.swift in Sources */, EAF49C7F1A3A4DE000B395DF /* UIImageExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Source/Animatable.swift b/Source/Animatable.swift new file mode 100644 index 0000000..2310183 --- /dev/null +++ b/Source/Animatable.swift @@ -0,0 +1,4 @@ +protocol Animatable { + var layer: CALayer { get } + var frame: CGRect { get } +} diff --git a/Source/AnimatableImageView.swift b/Source/AnimatableImageView.swift new file mode 100644 index 0000000..d95b3c6 --- /dev/null +++ b/Source/AnimatableImageView.swift @@ -0,0 +1,44 @@ +import UIKit +import ImageIO +import Runes + +public class AnimatableImageView: UIImageView, Animatable { + var animator: Animator? + + public var isAnimatingGIF: Bool { + return animator?.isAnimating ?? isAnimating() + } + + public func prepareForAnimation(imageNamed imageName: String) { + let path = NSBundle.mainBundle().bundlePath.stringByAppendingPathComponent(imageName) + prepareForAnimation <^> NSData(contentsOfFile: path) + } + + public func prepareForAnimation(imageData data: NSData) { + image = UIImage(data: data) + animator = Animator(data: data, delegate: self) + } + + public func animateWithImage(named imageName: String) { + prepareForAnimation(imageNamed: imageName) + startAnimatingGIF() + } + + public func animateWithImageData(#data: NSData) { + prepareForAnimation(imageData: data) + startAnimatingGIF() + } + + override public func displayLayer(layer: CALayer!) { + image = animator?.currentFrame? + } + + public func startAnimatingGIF() { + animator?.resumeAnimation() ?? startAnimating() + } + + public func stopAnimatingGIF() { + animator?.pauseAnimation() ?? stopAnimating() + } +} + diff --git a/Source/AnimatedImage.swift b/Source/Animator.swift old mode 100755 new mode 100644 similarity index 56% rename from Source/AnimatedImage.swift rename to Source/Animator.swift index 1afae05..f11954b --- a/Source/AnimatedImage.swift +++ b/Source/Animator.swift @@ -2,77 +2,33 @@ import UIKit import ImageIO import Runes -public class AnimatedImage: UIImage { - // MARK: - Constants +class Animator: NSObject { let maxTimeStep = 1.0 - - // MARK: - Public Properties - var delegate: UIImageView? var animatedFrames = [AnimatedFrame]() var totalDuration: NSTimeInterval = 0.0 - - override public var size: CGSize { - return frameAtIndex(0)?.size ?? CGSizeZero - } - - // MARK: - Private Properties - private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: "updateCurrentFrame") + let delegate: Animatable private var currentFrameIndex = 0 private var timeSinceLastFrameChange: NSTimeInterval = 0.0 + private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: "updateCurrentFrame") - // MARK: - Computed Properties var currentFrame: UIImage? { return frameAtIndex(currentFrameIndex) } - private var isAnimated: Bool { - return totalDuration != 0.0 + var isAnimating: Bool { + return !displayLink.paused } - // MARK: - Initializers - required public init(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - public override convenience init(data: NSData) { - self.init(data: data, size: CGSizeZero) - } - - required public init(data: NSData, size: CGSize) { - super.init() - + required init(data: NSData, delegate: Animatable) { let imageSource = CGImageSourceCreateWithData(data, nil) + self.delegate = delegate + super.init() attachDisplayLink() - curry(prepareFrames) <^> imageSource <*> size + curry(prepareFrames) <^> imageSource <*> delegate.frame.size pauseAnimation() } - // MARK: - Factories - public class func animatedImageWithName(name: String) -> AnimatedImage? { - let path = NSBundle.mainBundle().bundlePath.stringByAppendingPathComponent(name) - return animatedImageWithData <^> NSData(contentsOfFile: path) - } - - public class func animatedImageWithData(data: NSData) -> AnimatedImage { - let size = UIImage.sizeForImageData(data) ?? CGSizeZero - return self(data: data, size: size) - } - - public class func animatedImageWithName(name: String, size: CGSize) -> AnimatedImage? { - let path = NSBundle.mainBundle().bundlePath.stringByAppendingPathComponent(name) - return curry(animatedImageWithData) <^> NSData(contentsOfFile: path) <*> size - } - - public class func animatedImageWithData(data: NSData, size: CGSize) -> AnimatedImage { - return self(data: data, size: size) - } - - // MARK: - Display Link Helpers - func attachDisplayLink() { - displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) - } - - // MARK: - Frame Methods + // MARK: - Frames private func prepareFrames(imageSource: CGImageSourceRef, size: CGSize) { let numberOfFrames = Int(CGImageSourceGetCount(imageSource)) animatedFrames.reserveCapacity(numberOfFrames) @@ -96,7 +52,7 @@ public class AnimatedImage: UIImage { } func updateCurrentFrame() { - if !isAnimated { return } + if totalDuration == 0 { return } timeSinceLastFrameChange += min(maxTimeStep, displayLink.duration) var frameDuration = animatedFrames[currentFrameIndex].duration @@ -104,7 +60,7 @@ public class AnimatedImage: UIImage { if timeSinceLastFrameChange >= frameDuration { timeSinceLastFrameChange -= frameDuration currentFrameIndex = ++currentFrameIndex % animatedFrames.count - delegate?.layer.setNeedsDisplay() + delegate.layer.setNeedsDisplay() } } @@ -114,12 +70,12 @@ public class AnimatedImage: UIImage { } func resumeAnimation() { - if isAnimated { + if totalDuration > 0 { displayLink.paused = false } } - func isAnimating() -> Bool { - return !displayLink.paused + func attachDisplayLink() { + displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) } } diff --git a/Source/UIImageView+Gifu.swift b/Source/UIImageView+Gifu.swift deleted file mode 100755 index 4463525..0000000 --- a/Source/UIImageView+Gifu.swift +++ /dev/null @@ -1,37 +0,0 @@ -import UIKit - -public extension UIImageView { - // MARK: - Computed Properties - var animatableImage: AnimatedImage? { - return image as? AnimatedImage - } - - var isAnimatingGIF: Bool { - return animatableImage?.isAnimating() ?? isAnimating() - } - - var animatable: Bool { - return animatableImage != .None - } - - // MARK: - Method Overrides - override public func displayLayer(layer: CALayer!) { - layer.contents = animatableImage?.currentFrame?.CGImage - } - - // MARK: - Setter Methods - public func setAnimatedImage(image: AnimatedImage) { - image.delegate = self - self.image = image - layer.setNeedsDisplay() - } - - // MARK: - Animation - func startAnimatingGIF() { - animatableImage?.resumeAnimation() ?? startAnimating() - } - - func stopAnimatingGIF() { - animatableImage?.pauseAnimation() ?? stopAnimating() - } -}