You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

378 lines
11 KiB

//
// M13ProgressViewMetro.m
// M13ProgressSuite
//
// Created by Brandon McQuilkin on 3/8/14.
// Copyright (c) 2014 Brandon McQuilkin. All rights reserved.
//
#import "M13ProgressViewMetro.h"
#import "M13ProgressViewMetroDotPolygon.h"
@interface M13ProgressViewMetro ()
/**The start progress for the progress animation.*/
@property (nonatomic, assign) CGFloat animationFromValue;
/**The end progress for the progress animation.*/
@property (nonatomic, assign) CGFloat animationToValue;
/**The start time interval for the animaiton.*/
@property (nonatomic, assign) CFTimeInterval animationStartTime;
/**Link to the display to keep animations in sync.*/
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation M13ProgressViewMetroDot
{
M13ProgressViewAction currentAction;
}
#pragma mark Initalization
- (id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
}
- (id)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
if (self) {
[self setup];
}
return self;
}
+ (id)layer
{
return [[M13ProgressViewMetroDot alloc] init];
}
- (void)setup
{
_highlighted = NO;
currentAction = M13ProgressViewActionNone;
_primaryColor = [UIColor colorWithRed:0 green:122/255.0 blue:1.0 alpha:1.0];
_secondaryColor = [UIColor colorWithRed:181/255.0 green:182/255.0 blue:183/255.0 alpha:1.0];
_successColor = [UIColor colorWithRed:63.0f/255.0f green:226.0f/255.0f blue:80.0f/255.0f alpha:1];
_failureColor = [UIColor colorWithRed:249.0f/255.0f green:37.0f/255.0f blue:0 alpha:1];
}
#pragma mark Setters
- (void)setHighlighted:(BOOL)highlighted
{
_highlighted = highlighted;
[self setNeedsDisplay];
}
- (void)setSuccessColor:(UIColor *)successColor
{
_successColor = successColor;
[self setNeedsDisplay];
}
- (void)setFailureColor:(UIColor *)failureColor
{
_failureColor = failureColor;
[self setNeedsDisplay];
}
- (void)setPrimaryColor:(UIColor *)primaryColor
{
_primaryColor = primaryColor;
[self setNeedsDisplay];
}
- (void)setSecondaryColor:(UIColor *)secondaryColor
{
_secondaryColor = secondaryColor;
[self setNeedsDisplay];
}
- (void)performAction:(M13ProgressViewAction)action animated:(BOOL)animated
{
currentAction = action;
[self setNeedsDisplay];
}
@end
@implementation M13ProgressViewMetro
{
BOOL animating;
NSUInteger currentNumberOfDots;
NSTimer *animationTimer;
UIBezierPath *animationPath;
NSMutableArray *dots;
CGFloat circleSize;
}
- (id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
}
- (void)setup
{
//Set defaults
self.clipsToBounds = YES;
animating = NO;
_numberOfDots = 6;
_dotSize = CGSizeMake(20, 20);
self.animationDuration = 1.5;
self.animationShape = M13ProgressViewMetroAnimationShapeEllipse;
self.primaryColor = [UIColor colorWithRed:0 green:122/255.0 blue:1.0 alpha:1.0];
self.secondaryColor = [UIColor colorWithRed:181/255.0 green:182/255.0 blue:183/255.0 alpha:1.0];
_successColor = [UIColor colorWithRed:63.0f/255.0f green:226.0f/255.0f blue:80.0f/255.0f alpha:1];
_failureColor = [UIColor colorWithRed:249.0f/255.0f green:37.0f/255.0f blue:0 alpha:1];
_metroDot = [[M13ProgressViewMetroDotPolygon alloc] init];
}
#pragma mark Properties
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (animating) {
[self stopAnimating];
[self beginAnimating];
}
}
- (BOOL)isAnimating
{
return animating;
}
- (void)setAnimationShape:(M13ProgressViewMetroAnimationShape)animationShape
{
_animationShape = animationShape;
if (_animationShape == M13ProgressViewMetroAnimationShapeEllipse) {
//Inset the size of the dot
CGFloat radius = MIN((self.bounds.size.width - _dotSize.width) / 2, (self.bounds.size.height - _dotSize.height) / 2);
//Create the path
animationPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2) radius:radius startAngle:M_PI_2 endAngle:(-M_PI_2 * 3) clockwise:YES];
} else if (_animationShape == M13ProgressViewMetroAnimationShapeRectangle) {
//Inset the size of the dot
CGRect pathRect = CGRectInset(self.bounds, _dotSize.width, _dotSize.height);
//Create the path
animationPath = [UIBezierPath bezierPath];
[animationPath moveToPoint:CGPointMake((pathRect.size.width / 2) + pathRect.origin.x, pathRect.size.height + pathRect.origin.y)];
[animationPath addLineToPoint:CGPointMake(pathRect.origin.x, pathRect.size.height + pathRect.origin.y)];
[animationPath addLineToPoint:CGPointMake(pathRect.origin.x, pathRect.origin.y)];
[animationPath addLineToPoint:CGPointMake(pathRect.origin.x + pathRect.size.width, pathRect.origin.y)];
[animationPath addLineToPoint:CGPointMake(pathRect.origin.x + pathRect.size.width, pathRect.origin.y + pathRect.size.height)];
[animationPath moveToPoint:CGPointMake((pathRect.size.width / 2) + pathRect.origin.x, pathRect.size.height + pathRect.origin.y)];
} else if (animationShape == M13ProgressViewMetroAnimationShapeLine) {
//Create the path
animationPath = [UIBezierPath bezierPath];
[animationPath moveToPoint:CGPointMake(-_dotSize.width, self.bounds.size.height / 2)];
[animationPath addLineToPoint:CGPointMake(self.bounds.size.width + _dotSize.width, self.bounds.size.height / 2)];
}
[self stopAnimating];
[self beginAnimating];
}
- (void)setNumberOfDots:(NSUInteger)numberOfDots
{
_numberOfDots = numberOfDots;
[self invalidateIntrinsicContentSize];
[self stopAnimating];
[self beginAnimating];
}
- (void)setDotSize:(CGSize)dotSize
{
_dotSize = dotSize;
[self invalidateIntrinsicContentSize];
[self stopAnimating];
[self beginAnimating];
}
- (void)performAction:(M13ProgressViewAction)action animated:(BOOL)animated
{
for (M13ProgressViewMetroDot *dot in dots) {
[dot performAction:action animated:animated];
}
}
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
{
if (animated == NO) {
if (_displayLink) {
//Kill running animations
[_displayLink invalidate];
_displayLink = nil;
}
[super setProgress:progress animated:NO];
[self showProgress];
} else {
_animationStartTime = CACurrentMediaTime();
_animationFromValue = self.progress;
_animationToValue = progress;
if (!_displayLink) {
//Create and setup the display link
[self.displayLink invalidate];
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateProgress:)];
[self.displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
} /*else {
//Reuse the current display link
}*/
}
}
- (void)animateProgress:(CADisplayLink *)displayLink
{
dispatch_async(dispatch_get_main_queue(), ^{
CGFloat dt = (displayLink.timestamp - self.animationStartTime) / self.animationDuration;
if (dt >= 1.0) {
//Order is important! Otherwise concurrency will cause errors, because setProgress: will detect an animation in progress and try to stop it by itself. Once over one, set to actual progress amount. Animation is over.
[self.displayLink invalidate];
self.displayLink = nil;
[super setProgress:self.animationToValue animated:NO];
[self showProgress];
return;
}
//Set progress
[super setProgress:self.animationFromValue + dt * (self.animationToValue - self.animationFromValue) animated:YES];
[self showProgress];
});
}
- (void)showProgress
{
static int pastIndexToHighlightTo = 0;
int indexToHighlightTo = (int)ceilf(_numberOfDots * (float)self.progress);
//Only perform the animation if necessary.
if (pastIndexToHighlightTo != indexToHighlightTo) {
for (int i = 0; i < _numberOfDots; i++) {
M13ProgressViewMetroDot *dot = dots[i];
if (i <= indexToHighlightTo && self.progress != 0) {
dot.highlighted = YES;
} else {
dot.highlighted = NO;
}
}
pastIndexToHighlightTo = indexToHighlightTo;
}
}
- (CGSize)intrinsicContentSize
{
//No real constraint on size.
if (_animationShape == M13ProgressViewMetroAnimationShapeEllipse || _animationShape == M13ProgressViewMetroAnimationShapeRectangle) {
return CGSizeMake(3 * _dotSize.width, 3 * _dotSize.height);
} else {
return CGSizeMake(_dotSize.width * _numberOfDots, _dotSize.height);
}
}
#pragma mark Animation
-(void)beginAnimating
{
if (!animating){
animating = YES;
currentNumberOfDots = 0;
dots = [[NSMutableArray alloc] init];
//add circles
animationTimer = [NSTimer scheduledTimerWithTimeInterval: 0.20 target: self
selector: @selector(createCircle) userInfo: nil repeats: YES];
}
}
-(void)createCircle
{
if (currentNumberOfDots<_numberOfDots){
currentNumberOfDots ++;
CGRect f;
if (_animationShape != M13ProgressViewMetroAnimationShapeLine) {
f = CGRectMake((self.frame.size.width - _dotSize.width) / 2 - 1, self.frame.size.height - _dotSize.height - 1, _dotSize.width, _dotSize.height);
} else {
f = CGRectMake(- _dotSize.width, self.bounds.size.height / 2, _dotSize.width, _dotSize.height);
}
M13ProgressViewMetroDot *dotLayer = [_metroDot copy];
dotLayer.frame = f;
[self.layer addSublayer:dotLayer];
[dots addObject:dotLayer];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.duration = self.animationDuration;
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.15f :0.60f :0.85f :0.4f];
[animation setCalculationMode:kCAAnimationPaced];
animation.path = animationPath.CGPath;
animation.repeatCount = HUGE_VALF;
if (_animationShape != M13ProgressViewMetroAnimationShapeLine) {
//No delay needed
[dotLayer addAnimation:animation forKey:@"metroAnimation"];
} else {
//Delay repeat
animation.repeatCount = 1;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = self.animationDuration * 2;
group.animations = @[animation];
group.repeatCount = HUGE_VALF;
[dotLayer addAnimation:group forKey:@"metroAnimation"];
}
} else {
[animationTimer invalidate];
}
}
-(void)stopAnimating
{
animating = NO;
[animationTimer invalidate];
for (CALayer *layer in dots) {
[layer removeAllAnimations];
[layer removeFromSuperlayer];
}
}
@end