Gifu/Source/AnimatableImageView.swift

129 lines
4.3 KiB
Swift
Raw Normal View History

2015-01-23 00:02:08 +00:00
import UIKit
2015-01-22 10:54:27 +00:00
2015-01-23 00:02:08 +00:00
/// A subclass of `UIImageView` that can be animated using an image name string or raw data.
2015-06-04 23:24:34 +00:00
public class AnimatableImageView: UIImageView {
/// Proxy object for preventing a reference cycle between the CADisplayLink and AnimatableImageView.
/// Source: http://merowing.info/2015/11/the-beauty-of-imperfection/
class TargetProxy {
private weak var target: AnimatableImageView?
init(target: AnimatableImageView) {
self.target = target
}
@objc func onScreenUpdate() {
2016-04-24 09:28:09 +00:00
target?.updateFrameIfNeeded()
}
}
2015-01-23 00:02:08 +00:00
/// An `Animator` instance that holds the frames of a specific image in memory.
var animator: Animator?
/// A flag to avoid invalidating the displayLink on deinit if it was never created
private var displayLinkInitialized: Bool = false
2015-06-04 23:24:34 +00:00
/// A display link that keeps calling the `updateFrame` method on every screen refresh.
lazy var displayLink: CADisplayLink = {
self.displayLinkInitialized = true
let display = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate))
display.paused = true
return display
}()
2015-01-22 10:54:27 +00:00
2015-06-04 23:36:47 +00:00
/// The size of the frame cache.
public var framePreloadCount = 50
/// Specifies whether the GIF frames should be pre-scaled to save memory. Default is **true**.
public var needsPrescaling = true
2015-01-23 00:02:08 +00:00
/// A computed property that returns whether the image view is animating.
2015-01-22 10:54:27 +00:00
public var isAnimatingGIF: Bool {
2015-06-04 23:24:34 +00:00
return !displayLink.paused
2015-01-22 10:54:27 +00:00
}
2016-03-10 22:54:51 +00:00
/// A computed property that returns the total number of frames in the GIF.
public var frameCount: Int {
return animator?.frameCount ?? 0
}
2015-01-23 00:02:08 +00:00
/// Prepares the frames using a GIF image file name, without starting the animation.
/// The file name should include the `.gif` extension.
///
/// - parameter imageName: The name of the GIF file. The method looks for the file in the app bundle.
2015-01-22 10:54:27 +00:00
public func prepareForAnimation(imageNamed imageName: String) {
let imagePath = NSBundle.mainBundle().bundleURL.URLByAppendingPathComponent(imageName)
2016-04-24 10:19:13 +00:00
guard let data = NSData(contentsOfURL: imagePath) else { return }
prepareForAnimation(imageData: data)
2015-01-22 10:54:27 +00:00
}
2015-01-23 00:02:08 +00:00
/// Prepares the frames using raw GIF image data, without starting the animation.
///
/// - parameter data: GIF image data.
2015-01-22 10:54:27 +00:00
public func prepareForAnimation(imageData data: NSData) {
image = UIImage(data: data)
2015-06-04 23:36:47 +00:00
animator = Animator(data: data, size: frame.size, contentMode: contentMode, framePreloadCount: framePreloadCount)
animator?.needsPrescaling = needsPrescaling
2015-06-04 23:36:47 +00:00
animator?.prepareFrames()
2015-06-04 23:24:34 +00:00
attachDisplayLink()
2015-01-22 10:54:27 +00:00
}
2015-01-23 00:02:08 +00:00
/// Prepares the frames using a GIF image file name and starts animating the image view.
///
/// - parameter imageName: The name of the GIF file. The method looks for the file in the app bundle.
2015-01-22 10:54:27 +00:00
public func animateWithImage(named imageName: String) {
prepareForAnimation(imageNamed: imageName)
startAnimatingGIF()
}
2015-01-23 00:02:08 +00:00
/// Prepares the frames using raw GIF image data and starts animating the image view.
///
/// - parameter data: GIF image data.
public func animateWithImageData(data: NSData) {
2015-01-22 10:54:27 +00:00
prepareForAnimation(imageData: data)
startAnimatingGIF()
}
/// Updates the `image` property of the image view if necessary. This method should not be called manually.
override public func displayLayer(layer: CALayer) {
2016-04-24 09:28:09 +00:00
image = animator?.currentFrameImage ?? image
2015-01-22 10:54:27 +00:00
}
2015-01-23 00:02:08 +00:00
/// Starts the image view animation.
2015-01-22 10:54:27 +00:00
public func startAnimatingGIF() {
2015-06-04 23:24:34 +00:00
if animator?.isAnimatable ?? false {
displayLink.paused = false
}
2015-01-22 10:54:27 +00:00
}
2015-01-23 00:02:08 +00:00
/// Stops the image view animation.
2015-01-22 10:54:27 +00:00
public func stopAnimatingGIF() {
2015-06-04 23:24:34 +00:00
displayLink.paused = true
2015-01-22 10:54:27 +00:00
}
2016-04-24 09:28:09 +00:00
/// Reset the image view values.
public func prepareForReuse() {
stopAnimatingGIF()
animator = nil
}
2016-04-24 09:28:09 +00:00
/// Update the current frame if needed.
func updateFrameIfNeeded() {
guard let animator = animator else { return }
animator.shouldChangeFrame(displayLink.duration) { hasNewFrame in
if hasNewFrame { self.layer.setNeedsDisplay() }
}
}
/// Invalidate the displayLink so it releases its target.
deinit {
if displayLinkInitialized {
displayLink.invalidate()
}
}
2015-06-04 23:24:34 +00:00
2015-06-04 23:36:47 +00:00
/// Attaches the display link.
func attachDisplayLink() {
2015-06-04 23:36:47 +00:00
displayLink.addToRunLoop(.mainRunLoop(), forMode: NSRunLoopCommonModes)
2015-06-04 23:24:34 +00:00
}
}