Created
January 11, 2021 22:01
-
-
Save SergLam/acdad1f566cbc5564afaa785c74fbdfc to your computer and use it in GitHub Desktop.
Animated counting UILabel for iOS in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Source - https://medium.com/@topLayoutGuide/swift-3-so-i-wanted-to-animate-a-label-14dd2b332ef9 | |
// | |
// https://github.com/dataxpress/UICountingLabel/ | |
// | |
import UIKit | |
enum CountingMethod { | |
case easeInOut | |
case easeIn | |
case easeOut | |
case linear | |
} | |
enum AnimationDuration { | |
case fixed(time: TimeInterval) | |
case laborious | |
case plodding | |
case strolling | |
case brisk | |
case noAnimation | |
var value: TimeInterval { | |
switch self { | |
case .fixed(let time): | |
return time | |
case .laborious: | |
return 20.0 | |
case .plodding: | |
return 15.0 | |
case .strolling: | |
return 8.0 | |
case .brisk: | |
return 2.0 | |
case .noAnimation: | |
return 0.0 | |
} | |
} | |
} | |
enum DecimalPoints { | |
case zero, one, two, ridiculous | |
var format: String { | |
switch self { | |
case .zero: | |
return "%.0f" | |
case .one: | |
return "%.1f" | |
case .two: | |
return "%.2f" | |
case .ridiculous: | |
return "%f" | |
} | |
} | |
} | |
final class CountingLabel: UILabel { | |
typealias OptionalCallback = (() -> Void) | |
typealias OptionalFormatBlock = ((Float) -> String) | |
var completion: OptionalCallback? | |
var animationDuration: AnimationDuration = .brisk | |
var decimalPoints: DecimalPoints = .zero | |
var countingMethod: CountingMethod = .easeInOut | |
var customFormatBlock: OptionalFormatBlock? | |
private var currentValue: Float { | |
if progress >= totalTime { return destinationValue } | |
return startingValue + (update(t: Float(progress / totalTime)) * (destinationValue - startingValue)) | |
} | |
private var rate: Float = 0 | |
private var startingValue: Float = 0 | |
private var destinationValue: Float = 0 | |
private var progress: TimeInterval = 0 | |
private var lastUpdate: TimeInterval = 0 | |
private var totalTime: TimeInterval = 0 | |
private var easingRate: Float = 0 | |
private var timer: CADisplayLink? | |
func count(from: Float, to: Float, duration: AnimationDuration = .strolling) { | |
startingValue = from | |
destinationValue = to | |
timer?.invalidate() | |
timer = nil | |
if duration.value == 0.0 { | |
setTextValue(value: to) | |
completion?() | |
return | |
} | |
easingRate = 3.0 | |
progress = 0.0 | |
totalTime = duration.value | |
lastUpdate = Date.timeIntervalSinceReferenceDate | |
rate = 3.0 | |
addDisplayLink() | |
} | |
func countFromCurrent(to: Float, duration: AnimationDuration = .strolling) { | |
count(from: currentValue, to: to, duration: duration) | |
} | |
func countFromZero(to: Float, duration: AnimationDuration = .strolling) { | |
count(from: 0, to: to, duration: duration) | |
} | |
func countFromZero(to: Int, duration: AnimationDuration = .strolling) { | |
let value: Float = Float(to) | |
count(from: 0, to: value, duration: duration) | |
} | |
func stop() { | |
timer?.invalidate() | |
timer = nil | |
progress = totalTime | |
completion?() | |
} | |
private func addDisplayLink() { | |
timer = CADisplayLink(target: self, selector: #selector(self.updateValue(timer:))) | |
timer?.add(to: .main, forMode: .default) | |
timer?.add(to: .main, forMode: .tracking) | |
} | |
private func update(t: Float) -> Float { | |
var t = t | |
switch countingMethod { | |
case .linear: | |
return t | |
case .easeIn: | |
return powf(t, rate) | |
case .easeInOut: | |
var sign: Float = 1 | |
if Int(rate) % 2 == 0 { sign = -1 } | |
t *= 2 | |
return t < 1 ? 0.5 * powf(t, rate) : (sign * 0.5) * (powf(t - 2, rate) + sign * 2) | |
case .easeOut: | |
return 1.0 - powf((1.0 - t), rate) | |
} | |
} | |
@objc | |
private func updateValue(timer: Timer) { | |
let now: TimeInterval = Date.timeIntervalSinceReferenceDate | |
progress += now - lastUpdate | |
lastUpdate = now | |
if progress >= totalTime { | |
self.timer?.invalidate() | |
self.timer = nil | |
progress = totalTime | |
} | |
setTextValue(value: currentValue) | |
if progress == totalTime { completion?() } | |
} | |
private func setTextValue(value: Float) { | |
text = String(format: customFormatBlock?(value) ?? decimalPoints.format, value) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment