2
0

DACircularProgressView.m 10.0 KB

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