QCountingLabel.h
/// 文本数字变化方式枚举
typedef NS_ENUM(NSUInteger, QCountingMethod) {
QCountingMethodEaseInOut, // 开始结束慢,中间快
QCountingMethodEaseIn, // 开始慢,结束快
QCountingMethodEaseOut, // 开始快,结束慢
QCountingMethodLinear // 匀速
};
@interface QCountingLabel : UILabel
/// 文本数字样式,默认为 @"%f"
@property (nonatomic, strong) NSString *format;
/// 文本数字分隔符样式,例如 @"###,##0.00"
@property (nonatomic, strong) NSString *positiveFormat;
/// 文本数字变化方式,默认为 EaseInOut
@property (nonatomic, assign) QCountingMethod method;
/// 文本数字变化时间,默认为 2.0
@property (nonatomic, assign) NSTimeInterval animationDuration;
/// 文本数字样式 Block
@property (nonatomic, copy) NSString *(^formatBlock)(CGFloat);
/// 富文本数字样式 Block
@property (nonatomic, copy) NSAttributedString *(^attributedFormatBlock)(CGFloat);
/// 文本数字变化完成回调 Block
@property (nonatomic, copy) void (^completionBlock)();
/**
* 文本数字在指定时间内从起始值变化到结束值
*
* @param frame 控件的 frame
* @param format 文本数字样式,默认为 @"%f"
* @param positiveFormat 文本数字分隔符样式
* @param method 文本数字变化方式,默认为 EaseInOut
* @param startValue 起始值
* @param endValue 结束值
* @param duration 变化时间
* @param completion 完成回调
*
* @return QCountingLabel 对象
*/
+ (instancetype)q_countingLabelWithFrame:(CGRect)frame
format:(NSString *)format
positiveFormat:(nullable NSString *)positiveFormat
method:(QCountingMethod)method
fromValue:(CGFloat)startValue
toValue:(CGFloat)endValue
withDuration:(NSTimeInterval)duration
completion:(void (^)())completion;
/**
* 文本数字从起始值变化到结束值
*
* <p> 默认变化时间 2.0 秒 <p>
*
* @param startValue 起始值
* @param endValue 结束值
*
* @return nil
*/
- (void)q_countFromValue:(CGFloat)startValue toValue:(CGFloat)endValue;
/**
* 文本数字在指定时间内从起始值变化到结束值
*
* @param startValue 起始值
* @param endValue 结束值
* @param duration 变化时间
*
* @return nil
*/
- (void)q_countFromValue:(CGFloat)startValue toValue:(CGFloat)endValue withDuration:(NSTimeInterval)duration;
/**
* 文本数字从当前值变化到结束值
*
* <p> 默认变化时间 2.0 秒 <p>
*
* @param endValue 结束值
*
* @return nil
*/
- (void)q_countFromCurrentValueToValue:(CGFloat)endValue;
/**
* 文本数字在指定时间内从当前值变化到结束值
*
* @param endValue 结束值
* @param duration 变化时间
*
* @return nil
*/
- (void)q_countFromCurrentValueToValue:(CGFloat)endValue withDuration:(NSTimeInterval)duration;
/**
* 文本数字从 0 变化到结束值
*
* <p> 默认变化时间 2.0 秒 <p>
*
* @param endValue 结束值
*
* @return nil
*/
- (void)q_countFromZeroToValue:(CGFloat)endValue;
/**
* 文本数字在指定时间内从 0 变化到结束值
*
* @param endValue 结束值
* @param duration 变化时间
*
* @return nil
*/
- (void)q_countFromZeroToValue:(CGFloat)endValue withDuration:(NSTimeInterval)duration;
@end
QCountingLabel.m
#ifndef kQLabelCounterRate
#define kQLabelCounterRate 3.0
#endif
NS_ASSUME_NONNULL_BEGIN
@interface QCountingLabel ()
@property (nonatomic, assign) CGFloat startingValue;
@property (nonatomic, assign) CGFloat destinationValue;
@property (nonatomic, assign) NSTimeInterval progress;
@property (nonatomic, assign) NSTimeInterval lastUpdate;
@property (nonatomic, assign) NSTimeInterval totalTime;
@property (nonatomic, assign) CGFloat easingRate;
@property (nonatomic, strong, nullable) CADisplayLink *timer;
@end
@implementation QCountingLabel
#pragma mark - 文本数字变化方法
/// 创建 QCountingLabel 对象
+ (instancetype)q_countingLabelWithFrame:(CGRect)frame
format:(NSString *)format
positiveFormat:(nullable NSString *)positiveFormat
method:(QCountingMethod)method
fromValue:(CGFloat)startValue
toValue:(CGFloat)endValue
withDuration:(NSTimeInterval)duration
completion:(void (^)())completion {
QCountingLabel *label = [[self alloc] initWithFrame:frame];
label.format = format;
label.positiveFormat = positiveFormat;
label.method = method;
label.completionBlock = completion;
[label q_countFromValue:startValue toValue:endValue withDuration:duration];
return label;
}
/// 文本数字从起始值变化到结束值
- (void)q_countFromValue:(CGFloat)startValue toValue:(CGFloat)endValue {
if (self.animationDuration == 0.0f) {
self.animationDuration = 2.0f;
}
[self q_countFromValue:startValue toValue:endValue withDuration:self.animationDuration];
}
/// 文本数字在指定时间内从起始值变化到结束值
- (void)q_countFromValue:(CGFloat)startValue toValue:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
self.startingValue = startValue;
self.destinationValue = endValue;
// remove any (possible) old timers
[self.timer invalidate];
self.timer = nil;
if (duration == 0.0) {
// No animation
[self q_setTextValue:endValue];
if (self.completionBlock) {
self.completionBlock();
}
return;
}
self.easingRate = 3.0f;
self.progress = 0;
self.totalTime = duration;
self.lastUpdate = [NSDate timeIntervalSinceReferenceDate];
if (self.format == nil) {
self.format = @"%f";
}
CADisplayLink *timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(q_timerUpdate:)];
timer.frameInterval = 2;
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
self.timer = timer;
}
/// 文本数字从当前值变化到结束值
- (void)q_countFromCurrentValueToValue:(CGFloat)endValue {
[self q_countFromValue:[self q_getCurrentValue] toValue:endValue];
}
/// 文本数字在指定时间内从当前值变化到结束值
- (void)q_countFromCurrentValueToValue:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
[self q_countFromValue:[self q_getCurrentValue] toValue:endValue withDuration:duration];
}
/// 文本数字从 0 变化到结束值
- (void)q_countFromZeroToValue:(CGFloat)endValue {
[self q_countFromValue:0.0f toValue:endValue];
}
/// 文本数字在指定时间内从 0 变化到结束值
- (void)q_countFromZeroToValue:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
[self q_countFromValue:0.0f toValue:endValue withDuration:duration];
}
/// format setter
- (void)setFormat:(NSString *)format {
_format = format;
// update label with new format
[self q_setTextValue:self.q_getCurrentValue];
}
#pragma mark - 工具方法
/// 定时器定时响应事件处理
- (void)q_timerUpdate:(NSTimer *)timer {
// update progress
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
self.progress += now - self.lastUpdate;
self.lastUpdate = now;
if (self.progress >= self.totalTime) {
[self.timer invalidate];
self.timer = nil;
self.progress = self.totalTime;
}
[self q_setTextValue:[self q_getCurrentValue]];
if (self.progress == self.totalTime) {
if (self.completionBlock) {
self.completionBlock();
}
}
}
/// 设置数值
- (void)q_setTextValue:(CGFloat)value {
if (self.attributedFormatBlock != nil) {
self.attributedText = self.attributedFormatBlock(value);
} else if (self.formatBlock != nil) {
self.text = self.formatBlock(value);
} else {
// check if counting with ints - cast to int
if ([self.format rangeOfString:@"%(.*)d" options:NSRegularExpressionSearch].location != NSNotFound ||
[self.format rangeOfString:@"%(.*)i"].location != NSNotFound) {
// 整型样式
self.text = [NSString stringWithFormat:self.format, (int)value];
} else if (self.positiveFormat.length > 0) {
// 带千分位分隔符的浮点型样式
NSString *str = [NSString stringWithFormat:self.format, value];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
[formatter setPositiveFormat:self.positiveFormat];
NSString *formatterString = [formatter stringFromNumber:[NSNumber numberWithFloat:[str floatValue]]];
self.text = formatterString;
} else {
// 普通浮点型样式
self.text = [NSString stringWithFormat:self.format, value];
}
}
}
/// 获取当前值
- (CGFloat)q_getCurrentValue {
if (self.progress >= self.totalTime) {
return self.destinationValue;
}
CGFloat percent = self.progress / self.totalTime;
CGFloat updateVal = [self update:percent];
return self.startingValue + (updateVal * (self.destinationValue - self.startingValue));
}
/// 更新数值
- (CGFloat)update:(CGFloat)t {
switch (self.method) {
case 0: {
int sign = 1;
int r = (int)kQLabelCounterRate;
if (r % 2 == 0) {
sign = -1;
}
t *= 2;
if (t < 1) {
return 0.5f * powf(t, kQLabelCounterRate);
} else {
return sign * 0.5f * (powf(t - 2, kQLabelCounterRate) + sign * 2);
}
break;
}
case 1: {
return powf(t, kQLabelCounterRate);
break;
}
case 2: {
return 1.0 - powf((1.0 - t), kQLabelCounterRate);
break;
}
case 3: {
return t;
break;
}
default:
return t;
}
}
@end