UILabel 数字动态变化

1、数字动态变化

  • 具体实现代码见 GitHub 源码 QExtension

  • QCountingLabel.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    /// 文本数字变化方式枚举
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    #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
  • 使用

    • 1、初始化

      • QCountingLabel 继承自 UILabel, 初始化和 UILabel 一样

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        // 创建 QCountingLabel 对象
        QCountingLabel *countingLabel = [[QCountingLabel alloc] initWithFrame:CGRectMake(0, 0, 300, 120)];
        [self.view addSubview:countingLabel];

        // 常规设置,QCountingLabel 继承 UILabel, 设置和 UILabel 一样
        countingLabel.center = self.view.center;
        countingLabel.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.5];
        countingLabel.font = [UIFont systemFontOfSize:50];
        countingLabel.textColor = [UIColor redColor];
        countingLabel.textAlignment = NSTextAlignmentCenter;
      • 也可以使用类方法一体式创建设置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        // 创建 QCountingLabel 对象
        QCountingLabel *countingLabel = [QCountingLabel q_countingLabelWithFrame:CGRectMake(50, 100, 300, 120)
        format:@"%f"
        positiveFormat:@"###,###.##"
        method:QCountingMethodEaseOut
        fromValue:20
        toValue:3048.64
        withDuration:10.0f
        completion:^{

        NSLog(@"completion");
        }];

        [self.view addSubview:countingLabel];
    • 2、设置文本样式

      • 不设置时默认为 @”%f”

        1
        2
        // 设置文本样式
        countingLabel.format = @"%d";
      • 也可以使用 block 设置普通样式或富文本样式

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        // 设置文本样式,使用 block 可以根据不同的值设置多种不同的样式
        countingLabel.formatBlock = ^NSString *(CGFloat value) {

        NSInteger years = value / 12;
        NSInteger months = (NSInteger)value % 12;

        if (years == 0) {

        return [NSString stringWithFormat: @"%ld 个月", (long)months];

        } else {

        return [NSString stringWithFormat: @"%ld 年, %ld 个月", (long)years, (long)months];
        }
        };
    • 3、设置文本分隔符样式

      • 设置金额等金融数字时可以奢姿带有千分位分隔符的浮点数样式

        1
        2
        // 设置分隔符样式
        countingLabel.positiveFormat = @"###,###.##";
    • 4、设置文本变化方式

      • 不设置时默认为 EaseInOut

        1
        2
        // 设置文本变化方式
        countingLabel.method = QCountingMethodLinear;
    • 5、设置变化范围及动画时间

      • 不设置时间时默认为 2 秒

        1
        2
        3
        [countingLabel q_countFromValue:10 toValue:100];

        [countingLabel q_countFromValue:10 toValue:100 withDuration:1.0f];
    • 6、设置变化完成时的回调

      • 数字变化完成时,可以设置回调,再执行其他的操作

        1
        2
        3
        4
        5
        // 设置变化完成时的回调
        countingLabel.completionBlock = ^void () {

        NSLog(@"completion");
        };

1.1 整数样式数字的变化

  • 创建设置代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @property (nonatomic, strong) QCountingLabel *countingLabel;

    // 创建 QCountingLabel 对象
    self.countingLabel = [[QCountingLabel alloc] initWithFrame:CGRectMake(0, 0, 300, 120)];
    [self.view addSubview:self.countingLabel];

    // 常规设置,QCountingLabel 继承 UILabel, 设置和 UILabel 一样
    self.countingLabel.center = self.view.center;
    self.countingLabel.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.5];
    self.countingLabel.font = [UIFont systemFontOfSize:50];
    self.countingLabel.textColor = [UIColor redColor];
    self.countingLabel.textAlignment = NSTextAlignmentCenter;

    // 设置文本样式
    self.countingLabel.format = @"%d";

    // 设置变化范围及动画时间
    [self.countingLabel q_countFromValue:10 toValue:1000 withDuration:1.0f];
  • 效果

    Label1

1.2 浮点数样式数字的变化

  • 创建设置代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @property (nonatomic, strong) QCountingLabel *countingLabel;

    // 创建 QCountingLabel 对象
    self.countingLabel = [[QCountingLabel alloc] initWithFrame:CGRectMake(0, 0, 300, 120)];
    [self.view addSubview:self.countingLabel];

    // 常规设置,QCountingLabel 继承 UILabel, 设置和 UILabel 一样
    self.countingLabel.center = self.view.center;
    self.countingLabel.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.5];
    self.countingLabel.font = [UIFont systemFontOfSize:50];
    self.countingLabel.textColor = [UIColor redColor];
    self.countingLabel.textAlignment = NSTextAlignmentCenter;

    // 设置文本样式
    self.countingLabel.format = @"%.2f";

    // 设置变化范围及动画时间
    [self.countingLabel q_countFromValue:0 toValue:3198.23 withDuration:1.0f];
  • 效果

    Label2

1.3 带有千分位分隔符的浮点数

  • 创建设置代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @property (nonatomic, strong) QCountingLabel *countingLabel;

    // 创建 QCountingLabel 对象
    self.countingLabel = [[QCountingLabel alloc] initWithFrame:CGRectMake(0, 0, 300, 120)];
    [self.view addSubview:self.countingLabel];

    // 常规设置,QCountingLabel 继承 UILabel, 设置和 UILabel 一样
    self.countingLabel.center = self.view.center;
    self.countingLabel.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.5];
    self.countingLabel.font = [UIFont systemFontOfSize:50];
    self.countingLabel.textColor = [UIColor redColor];
    self.countingLabel.textAlignment = NSTextAlignmentCenter;

    // 设置文本样式
    self.countingLabel.format = @"%.2f";

    // 设置分隔符样式
    self.countingLabel.positiveFormat = @"###,###.##";

    // 设置变化范围及动画时间
    [self.countingLabel q_countFromValue:0 toValue:3048.64 withDuration:1.0f];
  • 效果

    Label3

文章目录
  1. 1. 1、数字动态变化
    1. 1.1. 1.1 整数样式数字的变化
    2. 1.2. 1.2 浮点数样式数字的变化
    3. 1.3. 1.3 带有千分位分隔符的浮点数
隐藏目录