UICountingLabel.m 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #import <QuartzCore/QuartzCore.h>
  2. #import "UICountingLabel.h"
  3. #if !__has_feature(objc_arc)
  4. #error UICountingLabel is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
  5. #endif
  6. #pragma mark - UILabelCounter
  7. #ifndef kUILabelCounterRate
  8. #define kUILabelCounterRate 3.0
  9. #endif
  10. @protocol UILabelCounter<NSObject>
  11. -(CGFloat)update:(CGFloat)t;
  12. @end
  13. @interface UILabelCounterLinear : NSObject<UILabelCounter>
  14. @end
  15. @interface UILabelCounterEaseIn : NSObject<UILabelCounter>
  16. @end
  17. @interface UILabelCounterEaseOut : NSObject<UILabelCounter>
  18. @end
  19. @interface UILabelCounterEaseInOut : NSObject<UILabelCounter>
  20. @end
  21. @interface UILabelCounterEaseInBounce : NSObject<UILabelCounter>
  22. @end
  23. @interface UILabelCounterEaseOutBounce : NSObject<UILabelCounter>
  24. @end
  25. @implementation UILabelCounterLinear
  26. -(CGFloat)update:(CGFloat)t
  27. {
  28. return t;
  29. }
  30. @end
  31. @implementation UILabelCounterEaseIn
  32. -(CGFloat)update:(CGFloat)t
  33. {
  34. return powf(t, kUILabelCounterRate);
  35. }
  36. @end
  37. @implementation UILabelCounterEaseOut
  38. -(CGFloat)update:(CGFloat)t{
  39. return 1.0-powf((1.0-t), kUILabelCounterRate);
  40. }
  41. @end
  42. @implementation UILabelCounterEaseInOut
  43. -(CGFloat) update: (CGFloat) t
  44. {
  45. t *= 2;
  46. if (t < 1)
  47. return 0.5f * powf (t, kUILabelCounterRate);
  48. else
  49. return 0.5f * (2.0f - powf(2.0 - t, kUILabelCounterRate));
  50. }
  51. @end
  52. @implementation UILabelCounterEaseInBounce
  53. -(CGFloat) update: (CGFloat) t {
  54. if (t < 4.0 / 11.0) {
  55. return 1.0 - (powf(11.0 / 4.0, 2) * powf(t, 2)) - t;
  56. }
  57. if (t < 8.0 / 11.0) {
  58. return 1.0 - (3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2)) - t;
  59. }
  60. if (t < 10.0 / 11.0) {
  61. return 1.0 - (15.0 /16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2)) - t;
  62. }
  63. return 1.0 - (63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2)) - t;
  64. }
  65. @end
  66. @implementation UILabelCounterEaseOutBounce
  67. -(CGFloat) update: (CGFloat) t {
  68. if (t < 4.0 / 11.0) {
  69. return powf(11.0 / 4.0, 2) * powf(t, 2);
  70. }
  71. if (t < 8.0 / 11.0) {
  72. return 3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2);
  73. }
  74. if (t < 10.0 / 11.0) {
  75. return 15.0 /16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2);
  76. }
  77. return 63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2);
  78. }
  79. @end
  80. #pragma mark - UICountingLabel
  81. @interface UICountingLabel ()
  82. @property CGFloat startingValue;
  83. @property CGFloat destinationValue;
  84. @property NSTimeInterval progress;
  85. @property NSTimeInterval lastUpdate;
  86. @property NSTimeInterval totalTime;
  87. @property CGFloat easingRate;
  88. @property (nonatomic, strong) CADisplayLink *timer;
  89. @property (nonatomic, strong) id<UILabelCounter> counter;
  90. @end
  91. @implementation UICountingLabel
  92. -(void)countFrom:(CGFloat)value to:(CGFloat)endValue {
  93. if (self.animationDuration == 0.0f) {
  94. self.animationDuration = 2.0f;
  95. }
  96. [self countFrom:value to:endValue withDuration:self.animationDuration];
  97. }
  98. -(void)countFrom:(CGFloat)startValue to:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
  99. self.startingValue = startValue;
  100. self.destinationValue = endValue;
  101. // remove any (possible) old timers
  102. [self.timer invalidate];
  103. self.timer = nil;
  104. if(self.format == nil) {
  105. self.format = @"%f";
  106. }
  107. if (duration == 0.0) {
  108. // No animation
  109. [self setTextValue:endValue];
  110. [self runCompletionBlock];
  111. return;
  112. }
  113. self.easingRate = 3.0f;
  114. self.progress = 0;
  115. self.totalTime = duration;
  116. self.lastUpdate = [NSDate timeIntervalSinceReferenceDate];
  117. switch(self.method)
  118. {
  119. case UILabelCountingMethodLinear:
  120. self.counter = [[UILabelCounterLinear alloc] init];
  121. break;
  122. case UILabelCountingMethodEaseIn:
  123. self.counter = [[UILabelCounterEaseIn alloc] init];
  124. break;
  125. case UILabelCountingMethodEaseOut:
  126. self.counter = [[UILabelCounterEaseOut alloc] init];
  127. break;
  128. case UILabelCountingMethodEaseInOut:
  129. self.counter = [[UILabelCounterEaseInOut alloc] init];
  130. break;
  131. case UILabelCountingMethodEaseOutBounce:
  132. self.counter = [[UILabelCounterEaseOutBounce alloc] init];
  133. break;
  134. case UILabelCountingMethodEaseInBounce:
  135. self.counter = [[UILabelCounterEaseInBounce alloc] init];
  136. break;
  137. }
  138. CADisplayLink *timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateValue:)];
  139. timer.frameInterval = 2;
  140. [timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
  141. [timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
  142. self.timer = timer;
  143. }
  144. - (void)countFromCurrentValueTo:(CGFloat)endValue {
  145. [self countFrom:[self currentValue] to:endValue];
  146. }
  147. - (void)countFromCurrentValueTo:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
  148. [self countFrom:[self currentValue] to:endValue withDuration:duration];
  149. }
  150. - (void)countFromZeroTo:(CGFloat)endValue {
  151. [self countFrom:0.0f to:endValue];
  152. }
  153. - (void)countFromZeroTo:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
  154. [self countFrom:0.0f to:endValue withDuration:duration];
  155. }
  156. - (void)updateValue:(NSTimer *)timer {
  157. // update progress
  158. NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
  159. self.progress += now - self.lastUpdate;
  160. self.lastUpdate = now;
  161. if (self.progress >= self.totalTime) {
  162. [self.timer invalidate];
  163. self.timer = nil;
  164. self.progress = self.totalTime;
  165. }
  166. [self setTextValue:[self currentValue]];
  167. if (self.progress == self.totalTime) {
  168. [self runCompletionBlock];
  169. }
  170. }
  171. - (void)setTextValue:(CGFloat)value
  172. {
  173. if (self.attributedFormatBlock != nil) {
  174. self.attributedText = self.attributedFormatBlock(value);
  175. }
  176. else if(self.formatBlock != nil)
  177. {
  178. self.text = self.formatBlock(value);
  179. }
  180. else
  181. {
  182. // check if counting with ints - cast to int
  183. if([self.format rangeOfString:@"%(.*)d" options:NSRegularExpressionSearch].location != NSNotFound || [self.format rangeOfString:@"%(.*)i"].location != NSNotFound )
  184. {
  185. NSString * str = [NSString stringWithFormat:self.format,(int)value];
  186. self.attributedText = [self setWithText:str];
  187. }
  188. else
  189. {
  190. NSString * str = [NSString stringWithFormat:self.format,value];
  191. self.attributedText = [self setWithText:str];
  192. }
  193. }
  194. }
  195. - (NSAttributedString *)setWithText:(NSString *)str
  196. {
  197. NSMutableAttributedString * attr = [[NSMutableAttributedString alloc] initWithString:str];
  198. [attr addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"PingFang SC" size:17] range:NSMakeRange(0, str.length - 1)];
  199. [attr addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"PingFang SC" size:14] range:NSMakeRange(str.length - 1, 1)];
  200. [attr addAttribute:NSForegroundColorAttributeName value:UIColorHex(0x0a0a0a) range:NSMakeRange(0, str.length)];
  201. return attr;
  202. }
  203. - (void)setFormat:(NSString *)format {
  204. _format = format;
  205. // update label with new format
  206. [self setTextValue:self.currentValue];
  207. }
  208. - (void)runCompletionBlock {
  209. if (self.completionBlock) {
  210. self.completionBlock();
  211. self.completionBlock = nil;
  212. }
  213. }
  214. - (CGFloat)currentValue {
  215. if (self.progress >= self.totalTime) {
  216. return self.destinationValue;
  217. }
  218. CGFloat percent = self.progress / self.totalTime;
  219. CGFloat updateVal = [self.counter update:percent];
  220. return self.startingValue + (updateVal * (self.destinationValue - self.startingValue));
  221. }
  222. @end