DACircularProgressView.m 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. //
  2. // DACircularProgressView.m
  3. // DACircularProgress
  4. //
  5. //
  6. #import "DACircularProgressView.h"
  7. #import <QuartzCore/QuartzCore.h>
  8. @interface DACircularProgressLayer : CALayer
  9. @property(nonatomic, strong) UIColor *trackTintColor;
  10. @property(nonatomic, strong) UIColor *progressTintColor;
  11. @property(nonatomic) NSInteger roundedCorners;
  12. @property(nonatomic) CGFloat thicknessRatio;
  13. @property(nonatomic) CGFloat progress;
  14. @property(nonatomic) NSInteger clockwiseProgress;
  15. @end
  16. @implementation DACircularProgressLayer
  17. @dynamic trackTintColor;
  18. @dynamic progressTintColor;
  19. @dynamic roundedCorners;
  20. @dynamic thicknessRatio;
  21. @dynamic progress;
  22. @dynamic clockwiseProgress;
  23. + (BOOL)needsDisplayForKey:(NSString *)key
  24. {
  25. if ([key isEqualToString:@"progress"]) {
  26. return YES;
  27. } else {
  28. return [super needsDisplayForKey:key];
  29. }
  30. }
  31. - (void)drawInContext:(CGContextRef)context
  32. {
  33. CGRect rect = self.bounds;
  34. CGPoint centerPoint = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f);
  35. CGFloat radius = MIN(rect.size.height, rect.size.width) / 2.0f;
  36. BOOL clockwise = (self.clockwiseProgress != 0);
  37. CGFloat progress = MIN(self.progress, 1.0f - FLT_EPSILON);
  38. CGFloat radians = 0;
  39. if (clockwise)
  40. {
  41. radians = (float)((progress * 2.0f * M_PI) - M_PI_2);
  42. }
  43. else
  44. {
  45. radians = (float)(3 * M_PI_2 - (progress * 2.0f * M_PI));
  46. }
  47. CGContextSetFillColorWithColor(context, self.trackTintColor.CGColor);
  48. CGMutablePathRef trackPath = CGPathCreateMutable();
  49. CGPathMoveToPoint(trackPath, NULL, centerPoint.x, centerPoint.y);
  50. CGPathAddArc(trackPath, NULL, centerPoint.x, centerPoint.y, radius, (float)(2.0f * M_PI), 0.0f, TRUE);
  51. CGPathCloseSubpath(trackPath);
  52. CGContextAddPath(context, trackPath);
  53. CGContextFillPath(context);
  54. CGPathRelease(trackPath);
  55. if (progress > 0.0f) {
  56. CGContextSetFillColorWithColor(context, self.progressTintColor.CGColor);
  57. CGMutablePathRef progressPath = CGPathCreateMutable();
  58. CGPathMoveToPoint(progressPath, NULL, centerPoint.x, centerPoint.y);
  59. CGPathAddArc(progressPath, NULL, centerPoint.x, centerPoint.y, radius, (float)(3.0f * M_PI_2), radians, !clockwise);
  60. CGPathCloseSubpath(progressPath);
  61. CGContextAddPath(context, progressPath);
  62. CGContextFillPath(context);
  63. CGPathRelease(progressPath);
  64. }
  65. if (progress > 0.0f && self.roundedCorners) {
  66. CGFloat pathWidth = radius * self.thicknessRatio;
  67. CGFloat xOffset = radius * (1.0f + ((1.0f - (self.thicknessRatio / 2.0f)) * cosf(radians)));
  68. CGFloat yOffset = radius * (1.0f + ((1.0f - (self.thicknessRatio / 2.0f)) * sinf(radians)));
  69. CGPoint endPoint = CGPointMake(xOffset, yOffset);
  70. CGRect startEllipseRect = (CGRect) {
  71. .origin.x = centerPoint.x - pathWidth / 2.0f,
  72. .origin.y = 0.0f,
  73. .size.width = pathWidth,
  74. .size.height = pathWidth
  75. };
  76. CGContextAddEllipseInRect(context, startEllipseRect);
  77. CGContextFillPath(context);
  78. CGRect endEllipseRect = (CGRect) {
  79. .origin.x = endPoint.x - pathWidth / 2.0f,
  80. .origin.y = endPoint.y - pathWidth / 2.0f,
  81. .size.width = pathWidth,
  82. .size.height = pathWidth
  83. };
  84. CGContextAddEllipseInRect(context, endEllipseRect);
  85. CGContextFillPath(context);
  86. }
  87. CGContextSetBlendMode(context, kCGBlendModeClear);
  88. CGFloat innerRadius = radius * (1.0f - self.thicknessRatio);
  89. CGRect clearRect = (CGRect) {
  90. .origin.x = centerPoint.x - innerRadius,
  91. .origin.y = centerPoint.y - innerRadius,
  92. .size.width = innerRadius * 2.0f,
  93. .size.height = innerRadius * 2.0f
  94. };
  95. CGContextAddEllipseInRect(context, clearRect);
  96. CGContextFillPath(context);
  97. }
  98. @end
  99. @interface DACircularProgressView ()
  100. @end
  101. @implementation DACircularProgressView
  102. + (void) initialize
  103. {
  104. if (self == [DACircularProgressView class]) {
  105. DACircularProgressView *circularProgressViewAppearance = [DACircularProgressView appearance];
  106. [circularProgressViewAppearance setTrackTintColor:[[UIColor whiteColor] colorWithAlphaComponent:0.3f]];
  107. [circularProgressViewAppearance setProgressTintColor:[UIColor whiteColor]];
  108. [circularProgressViewAppearance setBackgroundColor:[UIColor clearColor]];
  109. [circularProgressViewAppearance setThicknessRatio:0.3f];
  110. [circularProgressViewAppearance setRoundedCorners:NO];
  111. [circularProgressViewAppearance setClockwiseProgress:YES];
  112. [circularProgressViewAppearance setIndeterminateDuration:2.0f];
  113. [circularProgressViewAppearance setIndeterminate:NO];
  114. }
  115. }
  116. + (Class)layerClass
  117. {
  118. return [DACircularProgressLayer class];
  119. }
  120. - (DACircularProgressLayer *)circularProgressLayer
  121. {
  122. return (DACircularProgressLayer *)self.layer;
  123. }
  124. - (id)init
  125. {
  126. return [super initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)];
  127. }
  128. - (void)didMoveToWindow
  129. {
  130. CGFloat windowContentsScale = self.window.screen.scale;
  131. self.circularProgressLayer.contentsScale = windowContentsScale;
  132. [self.circularProgressLayer setNeedsDisplay];
  133. }
  134. #pragma mark - Progress
  135. - (CGFloat)progress
  136. {
  137. return self.circularProgressLayer.progress;
  138. }
  139. - (void)setProgress:(CGFloat)progress
  140. {
  141. [self setProgress:progress animated:NO];
  142. }
  143. - (void)setProgress:(CGFloat)progress animated:(BOOL)animated
  144. {
  145. [self setProgress:progress animated:animated initialDelay:0.0];
  146. }
  147. - (void)setProgress:(CGFloat)progress
  148. animated:(BOOL)animated
  149. initialDelay:(CFTimeInterval)initialDelay
  150. {
  151. [self.layer removeAnimationForKey:@"indeterminateAnimation"];
  152. [self.circularProgressLayer removeAnimationForKey:@"progress"];
  153. CGFloat pinnedProgress = MIN(MAX(progress, 0.0f), 1.0f);
  154. if (animated) {
  155. CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"];
  156. animation.duration = fabs(self.progress - pinnedProgress); // Same duration as UIProgressView animation
  157. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  158. animation.fillMode = kCAFillModeForwards;
  159. animation.fromValue = [NSNumber numberWithFloat:self.progress];
  160. animation.toValue = [NSNumber numberWithFloat:pinnedProgress];
  161. animation.beginTime = CACurrentMediaTime() + initialDelay;
  162. animation.delegate = self;
  163. [self.circularProgressLayer addAnimation:animation forKey:@"progress"];
  164. } else {
  165. [self.circularProgressLayer setNeedsDisplay];
  166. self.circularProgressLayer.progress = pinnedProgress;
  167. }
  168. }
  169. - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
  170. {
  171. NSNumber *pinnedProgressNumber = [animation valueForKey:@"toValue"];
  172. self.circularProgressLayer.progress = [pinnedProgressNumber floatValue];
  173. }
  174. #pragma mark - UIAppearance methods
  175. - (UIColor *)trackTintColor
  176. {
  177. return self.circularProgressLayer.trackTintColor;
  178. }
  179. - (void)setTrackTintColor:(UIColor *)trackTintColor
  180. {
  181. self.circularProgressLayer.trackTintColor = trackTintColor;
  182. [self.circularProgressLayer setNeedsDisplay];
  183. }
  184. - (UIColor *)progressTintColor
  185. {
  186. return self.circularProgressLayer.progressTintColor;
  187. }
  188. - (void)setProgressTintColor:(UIColor *)progressTintColor
  189. {
  190. self.circularProgressLayer.progressTintColor = progressTintColor;
  191. [self.circularProgressLayer setNeedsDisplay];
  192. }
  193. - (NSInteger)roundedCorners
  194. {
  195. return self.roundedCorners;
  196. }
  197. - (void)setRoundedCorners:(NSInteger)roundedCorners
  198. {
  199. self.circularProgressLayer.roundedCorners = roundedCorners;
  200. [self.circularProgressLayer setNeedsDisplay];
  201. }
  202. - (CGFloat)thicknessRatio
  203. {
  204. return self.circularProgressLayer.thicknessRatio;
  205. }
  206. - (void)setThicknessRatio:(CGFloat)thicknessRatio
  207. {
  208. self.circularProgressLayer.thicknessRatio = MIN(MAX(thicknessRatio, 0.f), 1.f);
  209. [self.circularProgressLayer setNeedsDisplay];
  210. }
  211. - (NSInteger)indeterminate
  212. {
  213. CAAnimation *spinAnimation = [self.layer animationForKey:@"indeterminateAnimation"];
  214. return (spinAnimation == nil ? 0 : 1);
  215. }
  216. - (void)setIndeterminate:(NSInteger)indeterminate
  217. {
  218. if (indeterminate) {
  219. if (!self.indeterminate) {
  220. CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
  221. spinAnimation.byValue = [NSNumber numberWithDouble:indeterminate > 0 ? 2.0f*M_PI : -2.0f*M_PI];
  222. spinAnimation.duration = self.indeterminateDuration;
  223. spinAnimation.repeatCount = HUGE_VALF;
  224. [self.layer addAnimation:spinAnimation forKey:@"indeterminateAnimation"];
  225. }
  226. } else {
  227. [self.layer removeAnimationForKey:@"indeterminateAnimation"];
  228. }
  229. }
  230. - (NSInteger)clockwiseProgress
  231. {
  232. return self.circularProgressLayer.clockwiseProgress;
  233. }
  234. - (void)setClockwiseProgress:(NSInteger)clockwiseProgres
  235. {
  236. self.circularProgressLayer.clockwiseProgress = clockwiseProgres;
  237. [self.circularProgressLayer setNeedsDisplay];
  238. }
  239. @end