MMMaterialDesignSpinner.m 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. //
  2. // MMMaterialDesignSpinner.m
  3. // Pods
  4. //
  5. // Created by Michael Maxwell on 12/28/14.
  6. //
  7. //
  8. #import "MMMaterialDesignSpinner.h"
  9. static NSString *kMMRingStrokeAnimationKey = @"mmmaterialdesignspinner.stroke";
  10. static NSString *kMMRingRotationAnimationKey = @"mmmaterialdesignspinner.rotation";
  11. @interface MMMaterialDesignSpinner ()
  12. @property (nonatomic, readonly) CAShapeLayer *progressLayer;
  13. @property (nonatomic, readwrite) BOOL isAnimating;
  14. @end
  15. @implementation MMMaterialDesignSpinner
  16. @synthesize progressLayer=_progressLayer;
  17. @synthesize hidesWhenStopped=_hidesWhenStopped;
  18. @synthesize activityIndicatorViewStyle=_activityIndicatorViewStyle;
  19. @synthesize color=_color;
  20. @synthesize timingFunction=_timingFunction;
  21. @synthesize duration=_duration;
  22. @synthesize percentComplete=_percentComplete;
  23. - (instancetype)initWithFrame:(CGRect)frame {
  24. if (self = [super initWithFrame:frame]) {
  25. [self initialize];
  26. }
  27. return self;
  28. }
  29. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  30. if (self = [super initWithCoder:aDecoder]) {
  31. [self initialize];
  32. }
  33. return self;
  34. }
  35. - (void)awakeFromNib
  36. {
  37. [super awakeFromNib];
  38. [self initialize];
  39. }
  40. - (void)initialize {
  41. self.duration = 1.5f;
  42. self.percentComplete = 0.f;
  43. _timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  44. [self.layer addSublayer:self.progressLayer];
  45. // See comment in resetAnimations on why this notification is used.
  46. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetAnimations) name:UIApplicationDidBecomeActiveNotification object:nil];
  47. [self invalidateIntrinsicContentSize];
  48. }
  49. - (void)dealloc
  50. {
  51. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
  52. }
  53. - (void)layoutSubviews {
  54. [super layoutSubviews];
  55. self.progressLayer.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
  56. [self invalidateIntrinsicContentSize];
  57. [self updatePath];
  58. }
  59. - (CGSize)intrinsicContentSize
  60. {
  61. return CGSizeMake(self.bounds.size.width, self.bounds.size.height);
  62. }
  63. - (void)tintColorDidChange {
  64. [super tintColorDidChange];
  65. self.progressLayer.strokeColor = self.tintColor.CGColor;
  66. }
  67. - (void)resetAnimations {
  68. // If the app goes to the background, returning it to the foreground causes the animation to stop (even though it's not explicitly stopped by our code). Resetting the animation seems to kick it back into gear.
  69. if (self.isAnimating) {
  70. [self stopAnimating];
  71. [self startAnimating];
  72. }
  73. }
  74. - (void)setAnimating:(BOOL)animate {
  75. (animate ? [self startAnimating] : [self stopAnimating]);
  76. }
  77. - (void)startAnimating {
  78. if (self.isAnimating)
  79. return;
  80. CABasicAnimation *animation = [CABasicAnimation animation];
  81. animation.keyPath = @"transform.rotation";
  82. animation.duration = self.duration / 0.375f;
  83. animation.fromValue = @(0.f);
  84. animation.toValue = @(2 * M_PI);
  85. animation.repeatCount = INFINITY;
  86. animation.removedOnCompletion = NO;
  87. [self.progressLayer addAnimation:animation forKey:kMMRingRotationAnimationKey];
  88. CABasicAnimation *headAnimation = [CABasicAnimation animation];
  89. headAnimation.keyPath = @"strokeStart";
  90. headAnimation.duration = self.duration / 1.5f;
  91. headAnimation.fromValue = @(0.f);
  92. headAnimation.toValue = @(0.25f);
  93. headAnimation.timingFunction = self.timingFunction;
  94. CABasicAnimation *tailAnimation = [CABasicAnimation animation];
  95. tailAnimation.keyPath = @"strokeEnd";
  96. tailAnimation.duration = self.duration / 1.5f;
  97. tailAnimation.fromValue = @(0.f);
  98. tailAnimation.toValue = @(1.f);
  99. tailAnimation.timingFunction = self.timingFunction;
  100. CABasicAnimation *endHeadAnimation = [CABasicAnimation animation];
  101. endHeadAnimation.keyPath = @"strokeStart";
  102. endHeadAnimation.beginTime = self.duration / 1.5f;
  103. endHeadAnimation.duration = self.duration / 3.0f;
  104. endHeadAnimation.fromValue = @(0.25f);
  105. endHeadAnimation.toValue = @(1.f);
  106. endHeadAnimation.timingFunction = self.timingFunction;
  107. CABasicAnimation *endTailAnimation = [CABasicAnimation animation];
  108. endTailAnimation.keyPath = @"strokeEnd";
  109. endTailAnimation.beginTime = self.duration / 1.5f;
  110. endTailAnimation.duration = self.duration / 3.0f;
  111. endTailAnimation.fromValue = @(1.f);
  112. endTailAnimation.toValue = @(1.f);
  113. endTailAnimation.timingFunction = self.timingFunction;
  114. CAAnimationGroup *animations = [CAAnimationGroup animation];
  115. [animations setDuration:self.duration];
  116. [animations setAnimations:@[headAnimation, tailAnimation, endHeadAnimation, endTailAnimation]];
  117. animations.repeatCount = INFINITY;
  118. animations.removedOnCompletion = NO;
  119. [self.progressLayer addAnimation:animations forKey:kMMRingStrokeAnimationKey];
  120. self.isAnimating = true;
  121. if (self.hidesWhenStopped) {
  122. self.hidden = NO;
  123. }
  124. }
  125. - (void)stopAnimating {
  126. if (!self.isAnimating)
  127. return;
  128. [self.progressLayer removeAnimationForKey:kMMRingRotationAnimationKey];
  129. [self.progressLayer removeAnimationForKey:kMMRingStrokeAnimationKey];
  130. self.isAnimating = false;
  131. if (self.hidesWhenStopped) {
  132. self.hidden = YES;
  133. }
  134. }
  135. #pragma mark - Private
  136. - (void)updatePath {
  137. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  138. CGFloat radius = MIN(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2) - self.progressLayer.lineWidth / 2;
  139. CGFloat startAngle = (CGFloat)(0);
  140. CGFloat endAngle = (CGFloat)(2*M_PI);
  141. UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  142. self.progressLayer.path = path.CGPath;
  143. self.progressLayer.strokeStart = 0.f;
  144. self.progressLayer.strokeEnd = self.percentComplete;
  145. }
  146. #pragma mark - Properties
  147. - (CAShapeLayer *)progressLayer {
  148. if (!_progressLayer) {
  149. _progressLayer = [CAShapeLayer layer];
  150. _progressLayer.strokeColor = self.tintColor.CGColor;
  151. _progressLayer.fillColor = nil;
  152. _progressLayer.lineWidth = 1.5f;
  153. }
  154. return _progressLayer;
  155. }
  156. - (BOOL)isAnimating {
  157. return _isAnimating;
  158. }
  159. - (CGFloat)lineWidth {
  160. return self.progressLayer.lineWidth;
  161. }
  162. - (NSString *)lineCap
  163. {
  164. return self.progressLayer.lineCap;
  165. }
  166. - (void)setLineWidth:(CGFloat)lineWidth {
  167. self.progressLayer.lineWidth = lineWidth;
  168. [self updatePath];
  169. }
  170. - (void)setLineCap:(NSString *)lineCap
  171. {
  172. self.progressLayer.lineCap = lineCap;
  173. [self updatePath];
  174. }
  175. - (void)setHidesWhenStopped:(BOOL)hidesWhenStopped {
  176. _hidesWhenStopped = hidesWhenStopped;
  177. self.hidden = !self.isAnimating && hidesWhenStopped;
  178. }
  179. - (void)setPercentComplete:(CGFloat)percentComplete {
  180. _percentComplete = percentComplete;
  181. if (_isAnimating) {
  182. return;
  183. }
  184. self.progressLayer.strokeStart = 0.f;
  185. self.progressLayer.strokeEnd = self.percentComplete;
  186. }
  187. @end