|
|
@@ -34,6 +34,29 @@
|
|
|
import UIKit
|
|
|
import ImageIO
|
|
|
|
|
|
+/// Protocol of `AnimatedImageView`.
|
|
|
+public protocol AnimatedImageViewDelegate: class {
|
|
|
+ /**
|
|
|
+ Called after the animatedImageView has finished each animation loop.
|
|
|
+
|
|
|
+ - parameter imageView: The animatedImageView that is being animated.
|
|
|
+ - parameter count: The looped count.
|
|
|
+ */
|
|
|
+ func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt)
|
|
|
+
|
|
|
+ /**
|
|
|
+ Called after the animatedImageView has reached the max repeat count.
|
|
|
+
|
|
|
+ - parameter imageView: The animatedImageView that is being animated.
|
|
|
+ */
|
|
|
+ func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView)
|
|
|
+}
|
|
|
+
|
|
|
+extension AnimatedImageViewDelegate {
|
|
|
+ public func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) {}
|
|
|
+ public func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) {}
|
|
|
+}
|
|
|
+
|
|
|
/// `AnimatedImageView` is a subclass of `UIImageView` for displaying animated image.
|
|
|
open class AnimatedImageView: UIImageView {
|
|
|
|
|
|
@@ -49,6 +72,27 @@ open class AnimatedImageView: UIImageView {
|
|
|
target?.updateFrame()
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// Enumeration that specifies repeat count of GIF
|
|
|
+ public enum RepeatCount: Equatable {
|
|
|
+ case once
|
|
|
+ case finite(count: UInt)
|
|
|
+ case infinite
|
|
|
+
|
|
|
+ public static func ==(lhs: RepeatCount, rhs: RepeatCount) -> Bool {
|
|
|
+ switch (lhs, rhs) {
|
|
|
+ case let (.finite(l), .finite(r)):
|
|
|
+ return l == r
|
|
|
+ case (.once, .once),
|
|
|
+ (.infinite, .infinite):
|
|
|
+ return true
|
|
|
+ case (.once, _),
|
|
|
+ (.infinite, _),
|
|
|
+ (.finite, _):
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// MARK: - Public property
|
|
|
/// Whether automatically play the animation when the view become visible. Default is true.
|
|
|
@@ -73,6 +117,20 @@ open class AnimatedImageView: UIImageView {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// The repeat count.
|
|
|
+ public var repeatCount = RepeatCount.infinite {
|
|
|
+ didSet {
|
|
|
+ if oldValue != repeatCount {
|
|
|
+ reset()
|
|
|
+ setNeedsDisplay()
|
|
|
+ layer.setNeedsDisplay()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Delegate of this `AnimatedImageView` object. See `AnimatedImageViewDelegate` protocol for more.
|
|
|
+ public weak var delegate: AnimatedImageViewDelegate?
|
|
|
|
|
|
// MARK: - Private property
|
|
|
/// `Animator` instance that holds the frames of a specific image in memory.
|
|
|
@@ -120,6 +178,10 @@ open class AnimatedImageView: UIImageView {
|
|
|
if self.isAnimating {
|
|
|
return
|
|
|
} else {
|
|
|
+ if animator?.isReachMaxRepeatCount ?? false {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
displayLink.isPaused = false
|
|
|
}
|
|
|
}
|
|
|
@@ -160,7 +222,12 @@ open class AnimatedImageView: UIImageView {
|
|
|
private func reset() {
|
|
|
animator = nil
|
|
|
if let imageSource = image?.kf.imageSource?.imageRef {
|
|
|
- animator = Animator(imageSource: imageSource, contentMode: contentMode, size: bounds.size, framePreloadCount: framePreloadCount)
|
|
|
+ animator = Animator(imageSource: imageSource,
|
|
|
+ contentMode: contentMode,
|
|
|
+ size: bounds.size,
|
|
|
+ framePreloadCount: framePreloadCount,
|
|
|
+ repeatCount: repeatCount)
|
|
|
+ animator?.delegate = self
|
|
|
animator?.needsPrescaling = needsPrescaling
|
|
|
animator?.prepareFramesAsynchronously()
|
|
|
}
|
|
|
@@ -199,10 +266,21 @@ open class AnimatedImageView: UIImageView {
|
|
|
|
|
|
if animator?.updateCurrentFrame(duration: duration) ?? false {
|
|
|
layer.setNeedsDisplay()
|
|
|
+
|
|
|
+ if animator?.isReachMaxRepeatCount ?? false {
|
|
|
+ stopAnimating()
|
|
|
+ delegate?.animatedImageViewDidFinishAnimating(self)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+extension AnimatedImageView: AnimatorDelegate {
|
|
|
+ func animator(_ animator: Animator, didPlayAnimationLoops count: UInt) {
|
|
|
+ delegate?.animatedImageView(self, didPlayAnimationLoops: count)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// Keeps a reference to an `Image` instance and its duration as a GIF frame.
|
|
|
struct AnimatedFrame {
|
|
|
var image: Image?
|
|
|
@@ -211,12 +289,17 @@ struct AnimatedFrame {
|
|
|
static let null: AnimatedFrame = AnimatedFrame(image: .none, duration: 0.0)
|
|
|
}
|
|
|
|
|
|
+protocol AnimatorDelegate: class {
|
|
|
+ func animator(_ animator: Animator, didPlayAnimationLoops count: UInt)
|
|
|
+}
|
|
|
+
|
|
|
// MARK: - Animator
|
|
|
class Animator {
|
|
|
// MARK: Private property
|
|
|
fileprivate let size: CGSize
|
|
|
fileprivate let maxFrameCount: Int
|
|
|
fileprivate let imageSource: CGImageSource
|
|
|
+ fileprivate let maxRepeatCount: AnimatedImageView.RepeatCount
|
|
|
|
|
|
fileprivate var animatedFrames = [AnimatedFrame]()
|
|
|
fileprivate let maxTimeStep: TimeInterval = 1.0
|
|
|
@@ -225,6 +308,8 @@ class Animator {
|
|
|
fileprivate var currentPreloadIndex = 0
|
|
|
fileprivate var timeSinceLastFrameChange: TimeInterval = 0.0
|
|
|
fileprivate var needsPrescaling = true
|
|
|
+ fileprivate var currentRepeatCount: UInt = 0
|
|
|
+ fileprivate weak var delegate: AnimatorDelegate?
|
|
|
|
|
|
/// Loop count of animated image.
|
|
|
private var loopCount = 0
|
|
|
@@ -232,6 +317,17 @@ class Animator {
|
|
|
var currentFrame: UIImage? {
|
|
|
return frame(at: currentFrameIndex)
|
|
|
}
|
|
|
+
|
|
|
+ var isReachMaxRepeatCount: Bool {
|
|
|
+ switch maxRepeatCount {
|
|
|
+ case .once:
|
|
|
+ return currentRepeatCount >= 1
|
|
|
+ case .finite(let maxCount):
|
|
|
+ return currentRepeatCount >= maxCount
|
|
|
+ case .infinite:
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
var contentMode = UIViewContentMode.scaleToFill
|
|
|
|
|
|
@@ -249,11 +345,16 @@ class Animator {
|
|
|
|
|
|
- returns: The animator object.
|
|
|
*/
|
|
|
- init(imageSource source: CGImageSource, contentMode mode: UIViewContentMode, size: CGSize, framePreloadCount count: Int) {
|
|
|
+ init(imageSource source: CGImageSource,
|
|
|
+ contentMode mode: UIViewContentMode,
|
|
|
+ size: CGSize,
|
|
|
+ framePreloadCount count: Int,
|
|
|
+ repeatCount: AnimatedImageView.RepeatCount) {
|
|
|
self.imageSource = source
|
|
|
self.contentMode = mode
|
|
|
self.size = size
|
|
|
self.maxFrameCount = count
|
|
|
+ self.maxRepeatCount = repeatCount
|
|
|
}
|
|
|
|
|
|
func frame(at index: Int) -> Image? {
|
|
|
@@ -334,10 +435,17 @@ class Animator {
|
|
|
let lastFrameIndex = currentFrameIndex
|
|
|
currentFrameIndex += 1
|
|
|
currentFrameIndex = currentFrameIndex % animatedFrames.count
|
|
|
-
|
|
|
+
|
|
|
if animatedFrames.count < frameCount {
|
|
|
preloadFrameAsynchronously(at: lastFrameIndex)
|
|
|
}
|
|
|
+
|
|
|
+ if currentFrameIndex == 0 {
|
|
|
+ currentRepeatCount += 1
|
|
|
+
|
|
|
+ delegate?.animator(self, didPlayAnimationLoops: currentRepeatCount)
|
|
|
+ }
|
|
|
+
|
|
|
return true
|
|
|
}
|
|
|
|