Files
2016-08-07 10:38:59 -04:00

272 lines
8.0 KiB
Objective-C

//
// 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