123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- import UIKit
- import ImageIO
- public protocol AnimatedImageViewDelegate: AnyObject {
-
- func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt)
-
- func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView)
- }
- extension AnimatedImageViewDelegate {
- public func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) {}
- public func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) {}
- }
- open class AnimatedImageView: UIImageView {
-
-
- class TargetProxy {
- private weak var target: AnimatedImageView?
-
- init(target: AnimatedImageView) {
- self.target = target
- }
-
- @objc func onScreenUpdate() {
- target?.updateFrame()
- }
- }
-
- 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, .finite(let count)),
- (.finite(let count), .once):
- return count == 1
- case (.once, _),
- (.infinite, _),
- (.finite, _):
- return false
- }
- }
- }
-
-
-
- public var autoPlayAnimatedImage = true
-
-
- public var framePreloadCount = 10
-
-
- public var needsPrescaling = true
-
-
- #if swift(>=4.2)
- public var runLoopMode = RunLoop.Mode.common {
- willSet {
- if runLoopMode == newValue {
- return
- } else {
- stopAnimating()
- displayLink.remove(from: .main, forMode: runLoopMode)
- displayLink.add(to: .main, forMode: newValue)
- startAnimating()
- }
- }
- }
- #else
- public var runLoopMode = RunLoopMode.commonModes {
- willSet {
- if runLoopMode == newValue {
- return
- } else {
- stopAnimating()
- displayLink.remove(from: .main, forMode: runLoopMode)
- displayLink.add(to: .main, forMode: newValue)
- startAnimating()
- }
- }
- }
- #endif
-
- public var repeatCount = RepeatCount.infinite {
- didSet {
- if oldValue != repeatCount {
- reset()
- setNeedsDisplay()
- layer.setNeedsDisplay()
- }
- }
- }
-
- public weak var delegate: AnimatedImageViewDelegate?
-
-
-
- private var animator: Animator?
-
-
- private var isDisplayLinkInitialized: Bool = false
-
-
- private lazy var displayLink: CADisplayLink = {
- self.isDisplayLinkInitialized = true
- let displayLink = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate))
- displayLink.add(to: .main, forMode: self.runLoopMode)
- displayLink.isPaused = true
- return displayLink
- }()
-
-
- override open var image: Image? {
- didSet {
- if image != oldValue {
- reset()
- }
- setNeedsDisplay()
- layer.setNeedsDisplay()
- }
- }
-
- deinit {
- if isDisplayLinkInitialized {
- displayLink.invalidate()
- }
- }
-
- override open var isAnimating: Bool {
- if isDisplayLinkInitialized {
- return !displayLink.isPaused
- } else {
- return super.isAnimating
- }
- }
-
-
- override open func startAnimating() {
- if self.isAnimating {
- return
- } else {
- if animator?.isReachMaxRepeatCount ?? false {
- return
- }
- displayLink.isPaused = false
- }
- }
-
-
- override open func stopAnimating() {
- super.stopAnimating()
- if isDisplayLinkInitialized {
- displayLink.isPaused = true
- }
- }
-
- override open func display(_ layer: CALayer) {
- if let currentFrame = animator?.currentFrame {
- layer.contents = currentFrame.cgImage
- } else {
- layer.contents = image?.cgImage
- }
- }
-
- override open func didMoveToWindow() {
- super.didMoveToWindow()
- didMove()
- }
-
- override open func didMoveToSuperview() {
- super.didMoveToSuperview()
- didMove()
- }
-
- override func shouldPreloadAllAnimation() -> Bool {
- return false
- }
-
-
- private func reset() {
- animator = nil
- if let imageSource = image?.kf.imageSource?.imageRef {
- animator = Animator(imageSource: imageSource,
- contentMode: contentMode,
- size: bounds.size,
- framePreloadCount: framePreloadCount,
- repeatCount: repeatCount)
- animator?.delegate = self
- animator?.needsPrescaling = needsPrescaling
- animator?.prepareFramesAsynchronously()
- }
- didMove()
- }
-
- private func didMove() {
- if autoPlayAnimatedImage && animator != nil {
- if let _ = superview, let _ = window {
- startAnimating()
- } else {
- stopAnimating()
- }
- }
- }
-
-
- private func updateFrame() {
- let duration: CFTimeInterval
-
-
-
- if #available(iOS 10.0, tvOS 10.0, *) {
-
-
- if displayLink.preferredFramesPerSecond == 0 {
- duration = displayLink.duration
- } else {
-
- duration = 1.0 / Double(displayLink.preferredFramesPerSecond)
- }
- } else {
- duration = displayLink.duration
- }
-
- 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)
- }
- }
- struct AnimatedFrame {
- var image: Image?
- let duration: TimeInterval
-
- static let null: AnimatedFrame = AnimatedFrame(image: .none, duration: 0.0)
- }
- protocol AnimatorDelegate: AnyObject {
- func animator(_ animator: Animator, didPlayAnimationLoops count: UInt)
- }
- class Animator {
-
- 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
- fileprivate var frameCount = 0
- fileprivate var currentFrameIndex = 0
- fileprivate var currentFrameIndexInBuffer = 0
- fileprivate var currentPreloadIndex = 0
- fileprivate var timeSinceLastFrameChange: TimeInterval = 0.0
- fileprivate var needsPrescaling = true
- fileprivate var currentRepeatCount: UInt = 0
- fileprivate weak var delegate: AnimatorDelegate?
-
-
- private var loopCount = 0
-
- var currentFrame: UIImage? {
- return frame(at: currentFrameIndexInBuffer)
- }
- var isReachMaxRepeatCount: Bool {
- switch maxRepeatCount {
- case .once:
- return currentRepeatCount >= 1
- case .finite(let maxCount):
- return currentRepeatCount >= maxCount
- case .infinite:
- return false
- }
- }
-
- var contentMode = UIView.ContentMode.scaleToFill
-
- private lazy var preloadQueue: DispatchQueue = {
- return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue")
- }()
-
-
- init(imageSource source: CGImageSource,
- contentMode mode: UIView.ContentMode,
- 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? {
- return animatedFrames[safe: index]?.image
- }
-
- func prepareFramesAsynchronously() {
- preloadQueue.async { [weak self] in
- self?.prepareFrames()
- }
- }
-
- private func prepareFrames() {
- frameCount = CGImageSourceGetCount(imageSource)
-
- if let properties = CGImageSourceCopyProperties(imageSource, nil),
- let gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary,
- let loopCount = gifInfo[kCGImagePropertyGIFLoopCount as String] as? Int
- {
- self.loopCount = loopCount
- }
-
- let frameToProcess = min(frameCount, maxFrameCount)
- animatedFrames.reserveCapacity(frameToProcess)
- animatedFrames = (0..<frameToProcess).reduce([]) { $0 + pure(prepareFrame(at: $1))}
- currentPreloadIndex = (frameToProcess + 1) % frameCount - 1
- }
-
- private func prepareFrame(at index: Int) -> AnimatedFrame {
-
- guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else {
- return AnimatedFrame.null
- }
-
- let defaultGIFFrameDuration = 0.100
- let frameDuration = imageSource.kf.gifProperties(at: index).map {
- gifInfo -> Double in
-
- let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as Double?
- let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as Double?
- let duration = unclampedDelayTime ?? delayTime ?? 0.0
-
-
- return duration > 0.011 ? duration : defaultGIFFrameDuration
- } ?? defaultGIFFrameDuration
-
- let image = Image(cgImage: imageRef)
- let scaledImage: Image?
-
- if needsPrescaling {
- scaledImage = image.kf.resize(to: size, for: contentMode)
- } else {
- scaledImage = image
- }
-
- return AnimatedFrame(image: scaledImage, duration: frameDuration)
- }
-
-
- func updateCurrentFrame(duration: CFTimeInterval) -> Bool {
- timeSinceLastFrameChange += min(maxTimeStep, duration)
- guard let frameDuration = animatedFrames[safe: currentFrameIndexInBuffer]?.duration, frameDuration <= timeSinceLastFrameChange else {
- return false
- }
-
- timeSinceLastFrameChange -= frameDuration
-
- let lastFrameIndex = currentFrameIndexInBuffer
- currentFrameIndexInBuffer += 1
- currentFrameIndexInBuffer = currentFrameIndexInBuffer % animatedFrames.count
-
- if animatedFrames.count < frameCount {
- preloadFrameAsynchronously(at: lastFrameIndex)
- }
-
- currentFrameIndex += 1
-
- if currentFrameIndex == frameCount {
- currentFrameIndex = 0
- currentRepeatCount += 1
- delegate?.animator(self, didPlayAnimationLoops: currentRepeatCount)
- }
- return true
- }
-
- private func preloadFrameAsynchronously(at index: Int) {
- preloadQueue.async { [weak self] in
- self?.preloadFrame(at: index)
- }
- }
-
- private func preloadFrame(at index: Int) {
- animatedFrames[index] = prepareFrame(at: currentPreloadIndex)
- currentPreloadIndex += 1
- currentPreloadIndex = currentPreloadIndex % frameCount
- }
- }
- extension CGImageSource: KingfisherCompatible { }
- extension Kingfisher where Base: CGImageSource {
- func gifProperties(at index: Int) -> [String: Double]? {
- let properties = CGImageSourceCopyPropertiesAtIndex(base, index, nil) as Dictionary?
- return properties?[kCGImagePropertyGIFDictionary] as? [String: Double]
- }
- }
- extension Array {
- fileprivate subscript(safe index: Int) -> Element? {
- return indices ~= index ? self[index] : nil
- }
- }
- private func pure<T>(_ value: T) -> [T] {
- return [value]
- }
|