DACircularProgressView.m 7.1 KB

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