123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- //
- // MMMaterialDesignSpinner.m
- // Pods
- //
- // Created by Michael Maxwell on 12/28/14.
- //
- //
- #import "MMMaterialDesignSpinner.h"
- static NSString *kMMRingStrokeAnimationKey = @"mmmaterialdesignspinner.stroke";
- static NSString *kMMRingRotationAnimationKey = @"mmmaterialdesignspinner.rotation";
- @interface MMMaterialDesignSpinner ()
- @property (nonatomic, readonly) CAShapeLayer *progressLayer;
- @property (nonatomic, readwrite) BOOL isAnimating;
- @end
- @implementation MMMaterialDesignSpinner
- @synthesize progressLayer=_progressLayer;
- @synthesize hidesWhenStopped=_hidesWhenStopped;
- @synthesize activityIndicatorViewStyle=_activityIndicatorViewStyle;
- @synthesize color=_color;
- @synthesize timingFunction=_timingFunction;
- @synthesize duration=_duration;
- @synthesize percentComplete=_percentComplete;
- - (instancetype)initWithFrame:(CGRect)frame {
- if (self = [super initWithFrame:frame]) {
- [self initialize];
- }
- return self;
- }
- - (instancetype)initWithCoder:(NSCoder *)aDecoder {
- if (self = [super initWithCoder:aDecoder]) {
- [self initialize];
- }
- return self;
- }
- - (void)awakeFromNib
- {
- [super awakeFromNib];
- [self initialize];
- }
- - (void)initialize {
- self.duration = 1.5f;
- self.percentComplete = 0.f;
- _timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
-
- [self.layer addSublayer:self.progressLayer];
-
- // See comment in resetAnimations on why this notification is used.
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetAnimations) name:UIApplicationDidBecomeActiveNotification object:nil];
- [self invalidateIntrinsicContentSize];
- }
- - (void)dealloc
- {
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
- }
- - (void)layoutSubviews {
- [super layoutSubviews];
-
- self.progressLayer.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
- [self invalidateIntrinsicContentSize];
- [self updatePath];
- }
- - (CGSize)intrinsicContentSize
- {
- return CGSizeMake(self.bounds.size.width, self.bounds.size.height);
- }
- - (void)tintColorDidChange {
- [super tintColorDidChange];
-
- self.progressLayer.strokeColor = self.tintColor.CGColor;
- }
- - (void)resetAnimations {
- // 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.
- if (self.isAnimating) {
- [self stopAnimating];
- [self startAnimating];
- }
- }
- - (void)setAnimating:(BOOL)animate {
- (animate ? [self startAnimating] : [self stopAnimating]);
- }
- - (void)startAnimating {
- if (self.isAnimating)
- return;
-
- CABasicAnimation *animation = [CABasicAnimation animation];
- animation.keyPath = @"transform.rotation";
- animation.duration = self.duration / 0.375f;
- animation.fromValue = @(0.f);
- animation.toValue = @(2 * M_PI);
- animation.repeatCount = INFINITY;
- animation.removedOnCompletion = NO;
- [self.progressLayer addAnimation:animation forKey:kMMRingRotationAnimationKey];
-
- CABasicAnimation *headAnimation = [CABasicAnimation animation];
- headAnimation.keyPath = @"strokeStart";
- headAnimation.duration = self.duration / 1.5f;
- headAnimation.fromValue = @(0.f);
- headAnimation.toValue = @(0.25f);
- headAnimation.timingFunction = self.timingFunction;
-
- CABasicAnimation *tailAnimation = [CABasicAnimation animation];
- tailAnimation.keyPath = @"strokeEnd";
- tailAnimation.duration = self.duration / 1.5f;
- tailAnimation.fromValue = @(0.f);
- tailAnimation.toValue = @(1.f);
- tailAnimation.timingFunction = self.timingFunction;
-
-
- CABasicAnimation *endHeadAnimation = [CABasicAnimation animation];
- endHeadAnimation.keyPath = @"strokeStart";
- endHeadAnimation.beginTime = self.duration / 1.5f;
- endHeadAnimation.duration = self.duration / 3.0f;
- endHeadAnimation.fromValue = @(0.25f);
- endHeadAnimation.toValue = @(1.f);
- endHeadAnimation.timingFunction = self.timingFunction;
-
- CABasicAnimation *endTailAnimation = [CABasicAnimation animation];
- endTailAnimation.keyPath = @"strokeEnd";
- endTailAnimation.beginTime = self.duration / 1.5f;
- endTailAnimation.duration = self.duration / 3.0f;
- endTailAnimation.fromValue = @(1.f);
- endTailAnimation.toValue = @(1.f);
- endTailAnimation.timingFunction = self.timingFunction;
-
- CAAnimationGroup *animations = [CAAnimationGroup animation];
- [animations setDuration:self.duration];
- [animations setAnimations:@[headAnimation, tailAnimation, endHeadAnimation, endTailAnimation]];
- animations.repeatCount = INFINITY;
- animations.removedOnCompletion = NO;
- [self.progressLayer addAnimation:animations forKey:kMMRingStrokeAnimationKey];
-
-
- self.isAnimating = true;
-
- if (self.hidesWhenStopped) {
- self.hidden = NO;
- }
- }
- - (void)stopAnimating {
- if (!self.isAnimating)
- return;
-
- [self.progressLayer removeAnimationForKey:kMMRingRotationAnimationKey];
- [self.progressLayer removeAnimationForKey:kMMRingStrokeAnimationKey];
- self.isAnimating = false;
-
- if (self.hidesWhenStopped) {
- self.hidden = YES;
- }
- }
- #pragma mark - Private
- - (void)updatePath {
- CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
- CGFloat radius = MIN(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2) - self.progressLayer.lineWidth / 2;
- CGFloat startAngle = (CGFloat)(0);
- CGFloat endAngle = (CGFloat)(2*M_PI);
- UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
- self.progressLayer.path = path.CGPath;
- self.progressLayer.strokeStart = 0.f;
- self.progressLayer.strokeEnd = self.percentComplete;
- }
- #pragma mark - Properties
- - (CAShapeLayer *)progressLayer {
- if (!_progressLayer) {
- _progressLayer = [CAShapeLayer layer];
- _progressLayer.strokeColor = self.tintColor.CGColor;
- _progressLayer.fillColor = nil;
- _progressLayer.lineWidth = 1.5f;
- }
- return _progressLayer;
- }
- - (BOOL)isAnimating {
- return _isAnimating;
- }
- - (CGFloat)lineWidth {
- return self.progressLayer.lineWidth;
- }
- - (NSString *)lineCap
- {
- return self.progressLayer.lineCap;
- }
- - (void)setLineWidth:(CGFloat)lineWidth {
- self.progressLayer.lineWidth = lineWidth;
- [self updatePath];
- }
- - (void)setLineCap:(NSString *)lineCap
- {
- self.progressLayer.lineCap = lineCap;
- [self updatePath];
- }
- - (void)setHidesWhenStopped:(BOOL)hidesWhenStopped {
- _hidesWhenStopped = hidesWhenStopped;
- self.hidden = !self.isAnimating && hidesWhenStopped;
- }
- - (void)setPercentComplete:(CGFloat)percentComplete {
- _percentComplete = percentComplete;
- if (_isAnimating) {
- return;
- }
- self.progressLayer.strokeStart = 0.f;
- self.progressLayer.strokeEnd = self.percentComplete;
- }
- @end
|