New file |
| | |
| | | // |
| | | // M13ProgressViewRadiative.m |
| | | // M13ProgressSuite |
| | | // |
| | | // Created by Brandon McQuilkin on 3/13/14. |
| | | // Copyright (c) 2014 Brandon McQuilkin. All rights reserved. |
| | | // |
| | | |
| | | #import "M13ProgressViewRadiative.h" |
| | | #import <QuartzCore/QuartzCore.h> |
| | | |
| | | @interface M13ProgressViewRadiative () |
| | | |
| | | /**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 M13ProgressViewRadiative |
| | | { |
| | | NSMutableArray *ripplePaths; |
| | | } |
| | | |
| | | - (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 own background color |
| | | self.backgroundColor = [UIColor clearColor]; |
| | | self.clipsToBounds = YES; |
| | | |
| | | //Set defauts |
| | | self.animationDuration = 1.0; |
| | | _originationPoint = CGPointMake(0.5, 0.5); |
| | | self.numberOfRipples = 10; |
| | | self.shape = M13ProgressViewRadiativeShapeCircle; |
| | | _rippleWidth = 1.0; |
| | | _ripplesRadius = 20; |
| | | _pulseWidth = 5; |
| | | _progressOutwards = YES; |
| | | |
| | | //Set default colors |
| | | 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]; |
| | | } |
| | | |
| | | #pragma mark Setters |
| | | |
| | | - (void)setOriginationPoint:(CGPoint)originationPoint |
| | | { |
| | | _originationPoint = originationPoint; |
| | | [self setNeedsLayout]; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | |
| | | - (void)setRipplesRadius:(CGFloat)ripplesRadius |
| | | { |
| | | _ripplesRadius = ripplesRadius; |
| | | [self setNeedsLayout]; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | |
| | | - (void)setNumberOfRipples:(NSUInteger)numberOfRipples |
| | | { |
| | | _numberOfRipples = numberOfRipples; |
| | | [self setNeedsLayout]; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | |
| | | - (void)setRippleWidth:(CGFloat)rippleWidth |
| | | { |
| | | _rippleWidth = rippleWidth; |
| | | for (UIBezierPath *path in ripplePaths) { |
| | | path.lineWidth = _rippleWidth; |
| | | } |
| | | [self setIndeterminate:self.indeterminate]; |
| | | } |
| | | |
| | | - (void)setShape:(M13ProgressViewRadiativeShape)shape |
| | | { |
| | | _shape = shape; |
| | | [self setNeedsLayout]; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | |
| | | - (void)setPulseWidth:(NSUInteger)pulseWidth |
| | | { |
| | | _pulseWidth = pulseWidth; |
| | | self.indeterminate = self.indeterminate; |
| | | } |
| | | |
| | | - (void)setProgressOutwards:(BOOL)progressOutwards |
| | | { |
| | | _progressOutwards = progressOutwards; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | |
| | | - (void)setPrimaryColor:(UIColor *)primaryColor |
| | | { |
| | | [super setPrimaryColor:primaryColor]; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | |
| | | - (void)setSecondaryColor:(UIColor *)secondaryColor |
| | | { |
| | | [super setSecondaryColor:secondaryColor]; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | |
| | | #pragma mark animations |
| | | |
| | | - (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 setNeedsDisplay]; |
| | | } 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 setNeedsDisplay]; |
| | | return; |
| | | } |
| | | |
| | | //Set progress |
| | | [super setProgress:self.animationFromValue + dt * (self.animationToValue - self.animationFromValue) animated:YES]; |
| | | [self setNeedsDisplay]; |
| | | |
| | | }); |
| | | } |
| | | |
| | | - (void)setIndeterminate:(BOOL)indeterminate |
| | | { |
| | | [super setIndeterminate:indeterminate]; |
| | | //Need animation |
| | | } |
| | | |
| | | #pragma mark Layout |
| | | |
| | | - (void)layoutSubviews |
| | | { |
| | | [super layoutSubviews]; |
| | | //Create the paths to draw the ripples |
| | | ripplePaths = [NSMutableArray array]; |
| | | for (int i = 0; i < _numberOfRipples - 1; i++) { |
| | | if (_shape == M13ProgressViewRadiativeShapeCircle) { |
| | | //If circular |
| | | UIBezierPath *path = [UIBezierPath bezierPath]; |
| | | //Calculate the radius |
| | | CGFloat radius = _ripplesRadius * ((float)i / (float)(_numberOfRipples - 1)); |
| | | //Draw the arc |
| | | [path moveToPoint:CGPointMake((_originationPoint.x * self.bounds.size.width)+ radius, _originationPoint.y * self.bounds.size.height)]; |
| | | [path addArcWithCenter:CGPointMake(self.bounds.size.width * _originationPoint.x, self.bounds.size.height * _originationPoint.y) radius:radius startAngle:0.0 endAngle:(2 * M_PI) clockwise:YES]; |
| | | //Set the width |
| | | path.lineWidth = _rippleWidth; |
| | | [ripplePaths addObject:path]; |
| | | } else if (_shape == M13ProgressViewRadiativeShapeSquare) { |
| | | //If square |
| | | CGFloat radius = _ripplesRadius * ((float)i / (float)(_numberOfRipples - 1)); |
| | | CGFloat delta = radius * (1 / sqrtf(2)); |
| | | UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake((_originationPoint.x * self.bounds.size.width) - delta, (_originationPoint.y * self.bounds.size.height) - delta, delta * 2, delta * 2)]; |
| | | path.lineWidth = _rippleWidth; |
| | | [ripplePaths addObject:path]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (CGSize)intrinsicContentSize |
| | | { |
| | | //The width and height should be set with constraints. Can't think of a good way to figure out the minimum size with the point and scale based size calculations. |
| | | return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric); |
| | | } |
| | | |
| | | #pragma mark Drawing |
| | | |
| | | - (void)drawRect:(CGRect)rect |
| | | { |
| | | [super drawRect:rect]; |
| | | //Get the current context |
| | | CGContextRef context = UIGraphicsGetCurrentContext(); |
| | | //For each of the paths draw it in the view. |
| | | NSEnumerator *enumerator; |
| | | if (_progressOutwards) { |
| | | enumerator = [ripplePaths objectEnumerator]; |
| | | } else { |
| | | enumerator = [ripplePaths reverseObjectEnumerator]; |
| | | } |
| | | |
| | | UIBezierPath *path; |
| | | int i = 0; |
| | | int indexOfLastFilledPath = (int)ceilf((float)self.progress * (float)_numberOfRipples); |
| | | while ((path = [enumerator nextObject])) { |
| | | //Set the path's color |
| | | if (!self.indeterminate) { |
| | | //Show progress |
| | | if (i <= indexOfLastFilledPath && self.progress != 0) { |
| | | //Highlighted |
| | | CGContextSetStrokeColorWithColor(context, self.primaryColor.CGColor); |
| | | CGContextAddPath(context, path.CGPath); |
| | | CGContextStrokePath(context); |
| | | } else { |
| | | //Not highlighted |
| | | CGContextSetStrokeColorWithColor(context, self.secondaryColor.CGColor); |
| | | CGContextAddPath(context, path.CGPath); |
| | | CGContextStrokePath(context); |
| | | } |
| | | i++; |
| | | } else { |
| | | //Indeterminate |
| | | |
| | | } |
| | | } |
| | | } |
| | | |
| | | @end |