You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
6.1 KiB
Objective-C
202 lines
6.1 KiB
Objective-C
//
|
|
// 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;
|
|
|
|
- (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;
|
|
_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];
|
|
}
|
|
|
|
- (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 updatePath];
|
|
}
|
|
|
|
- (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 = 0.f;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
- (void)setLineWidth:(CGFloat)lineWidth {
|
|
self.progressLayer.lineWidth = lineWidth;
|
|
[self updatePath];
|
|
}
|
|
|
|
- (void)setHidesWhenStopped:(BOOL)hidesWhenStopped {
|
|
_hidesWhenStopped = hidesWhenStopped;
|
|
self.hidden = !self.isAnimating && hidesWhenStopped;
|
|
}
|
|
|
|
@end
|