Skip to content

Commit 40c0ee1

Browse files
Added radiative progress view
1 parent 7cec41f commit 40c0ee1

File tree

4 files changed

+450
-0
lines changed

4 files changed

+450
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// M13ProgressViewRadiative.h
3+
// M13ProgressSuite
4+
//
5+
// Created by Brandon McQuilkin on 3/13/14.
6+
// Copyright (c) 2014 Brandon McQuilkin. All rights reserved.
7+
//
8+
9+
#import "M13ProgressView.h"
10+
11+
typedef enum {
12+
M13ProgressViewRadiativeShapeCircle,
13+
M13ProgressViewRadiativeShapeSquare
14+
} M13ProgressViewRadiativeShape;
15+
16+
/**A progress view that displays progress via "Radiative" rings around a central point.*/
17+
@interface M13ProgressViewRadiative : M13ProgressView
18+
19+
/**@name Appearance*/
20+
/**The point where the wave fronts originate from. The point is defined in percentages, top left being {0, 0}, and the bottom right being {1, 1}.*/
21+
@property (nonatomic, assign) CGPoint originationPoint;
22+
/**The distance of the last ripple from the origination point.*/
23+
@property (nonatomic, assign) CGFloat ripplesRadius;
24+
/**The width of the ripples.*/
25+
@property (nonatomic, assign) CGFloat rippleWidth;
26+
/**The shape of the radiative ripples*/
27+
@property (nonatomic, assign) M13ProgressViewRadiativeShape shape;
28+
/**The number of ripples the progress view displays.*/
29+
@property (nonatomic, assign) NSUInteger numberOfRipples;
30+
/**The number of ripples the indeterminate pulse animation is.*/
31+
@property (nonatomic, assign) NSUInteger pulseWidth;
32+
/**The direction of the progress. If set to yes, the progress will be outward, of set to no, it will be inwards.*/
33+
@property (nonatomic, assign) BOOL progressOutwards;
34+
35+
@end
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
//
2+
// M13ProgressViewRadiative.m
3+
// M13ProgressSuite
4+
//
5+
// Created by Brandon McQuilkin on 3/13/14.
6+
// Copyright (c) 2014 Brandon McQuilkin. All rights reserved.
7+
//
8+
9+
#import "M13ProgressViewRadiative.h"
10+
#import <QuartzCore/QuartzCore.h>
11+
12+
@interface M13ProgressViewRadiative ()
13+
14+
/**The start progress for the progress animation.*/
15+
@property (nonatomic, assign) CGFloat animationFromValue;
16+
/**The end progress for the progress animation.*/
17+
@property (nonatomic, assign) CGFloat animationToValue;
18+
/**The start time interval for the animaiton.*/
19+
@property (nonatomic, assign) CFTimeInterval animationStartTime;
20+
/**Link to the display to keep animations in sync.*/
21+
@property (nonatomic, strong) CADisplayLink *displayLink;
22+
23+
@end
24+
25+
@implementation M13ProgressViewRadiative
26+
{
27+
NSMutableArray *ripplePaths;
28+
}
29+
30+
- (id)init
31+
{
32+
self = [super init];
33+
if (self) {
34+
[self setup];
35+
}
36+
return self;
37+
}
38+
39+
- (id)initWithFrame:(CGRect)frame
40+
{
41+
self = [super initWithFrame:frame];
42+
if (self) {
43+
[self setup];
44+
}
45+
return self;
46+
}
47+
48+
- (id)initWithCoder:(NSCoder *)aDecoder
49+
{
50+
self = [super initWithCoder:aDecoder];
51+
if (self) {
52+
[self setup];
53+
}
54+
return self;
55+
}
56+
57+
- (void)setup
58+
{
59+
//Set own background color
60+
self.backgroundColor = [UIColor clearColor];
61+
self.clipsToBounds = YES;
62+
63+
//Set defauts
64+
self.animationDuration = 1.0;
65+
_originationPoint = CGPointMake(0.5, 0.5);
66+
self.numberOfRipples = 10;
67+
self.shape = M13ProgressViewRadiativeShapeCircle;
68+
_rippleWidth = 1.0;
69+
_ripplesRadius = 20;
70+
_pulseWidth = 5;
71+
_progressOutwards = YES;
72+
73+
//Set default colors
74+
self.primaryColor = [UIColor colorWithRed:0 green:122/255.0 blue:1.0 alpha:1.0];
75+
self.secondaryColor = [UIColor colorWithRed:181/255.0 green:182/255.0 blue:183/255.0 alpha:1.0];
76+
}
77+
78+
#pragma mark Setters
79+
80+
- (void)setOriginationPoint:(CGPoint)originationPoint
81+
{
82+
_originationPoint = originationPoint;
83+
[self setNeedsLayout];
84+
[self setNeedsDisplay];
85+
}
86+
87+
- (void)setRipplesRadius:(CGFloat)ripplesRadius
88+
{
89+
_ripplesRadius = ripplesRadius;
90+
[self setNeedsLayout];
91+
[self setNeedsDisplay];
92+
}
93+
94+
- (void)setNumberOfRipples:(NSUInteger)numberOfRipples
95+
{
96+
_numberOfRipples = numberOfRipples;
97+
[self setNeedsLayout];
98+
[self setNeedsDisplay];
99+
}
100+
101+
- (void)setRippleWidth:(CGFloat)rippleWidth
102+
{
103+
_rippleWidth = rippleWidth;
104+
for (UIBezierPath *path in ripplePaths) {
105+
path.lineWidth = _rippleWidth;
106+
}
107+
[self setIndeterminate:self.indeterminate];
108+
}
109+
110+
- (void)setShape:(M13ProgressViewRadiativeShape)shape
111+
{
112+
_shape = shape;
113+
[self setNeedsLayout];
114+
[self setNeedsDisplay];
115+
}
116+
117+
- (void)setPulseWidth:(NSUInteger)pulseWidth
118+
{
119+
_pulseWidth = pulseWidth;
120+
self.indeterminate = self.indeterminate;
121+
}
122+
123+
- (void)setProgressOutwards:(BOOL)progressOutwards
124+
{
125+
_progressOutwards = progressOutwards;
126+
[self setNeedsDisplay];
127+
}
128+
129+
- (void)setPrimaryColor:(UIColor *)primaryColor
130+
{
131+
[super setPrimaryColor:primaryColor];
132+
[self setNeedsDisplay];
133+
}
134+
135+
- (void)setSecondaryColor:(UIColor *)secondaryColor
136+
{
137+
[super setSecondaryColor:secondaryColor];
138+
[self setNeedsDisplay];
139+
}
140+
141+
#pragma mark animations
142+
143+
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
144+
{
145+
if (animated == NO) {
146+
if (_displayLink) {
147+
//Kill running animations
148+
[_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
149+
_displayLink = nil;
150+
}
151+
[super setProgress:progress animated:NO];
152+
[self setNeedsDisplay];
153+
} else {
154+
_animationStartTime = CACurrentMediaTime();
155+
_animationFromValue = self.progress;
156+
_animationToValue = progress;
157+
if (!_displayLink) {
158+
//Create and setup the display link
159+
[self.displayLink removeFromRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
160+
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateProgress:)];
161+
[self.displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
162+
} /*else {
163+
//Reuse the current display link
164+
}*/
165+
}
166+
}
167+
168+
- (void)animateProgress:(CADisplayLink *)displayLink
169+
{
170+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
171+
CGFloat dt = (displayLink.timestamp - _animationStartTime) / self.animationDuration;
172+
if (dt >= 1.0) {
173+
//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.
174+
[self.displayLink removeFromRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
175+
self.displayLink = nil;
176+
dispatch_async(dispatch_get_main_queue(), ^{
177+
[super setProgress:_animationToValue animated:NO];
178+
[self setNeedsDisplay];
179+
});
180+
return;
181+
}
182+
183+
dispatch_async(dispatch_get_main_queue(), ^{
184+
//Set progress
185+
[super setProgress:_animationFromValue + dt * (_animationToValue - _animationFromValue) animated:YES];
186+
[self setNeedsDisplay];
187+
});
188+
189+
});
190+
}
191+
192+
- (void)setIndeterminate:(BOOL)indeterminate
193+
{
194+
[super setIndeterminate:indeterminate];
195+
//Need animation
196+
}
197+
198+
#pragma mark Layout
199+
200+
- (void)layoutSubviews
201+
{
202+
[super layoutSubviews];
203+
//Create the paths to draw the ripples
204+
ripplePaths = [NSMutableArray array];
205+
for (int i = 0; i < _numberOfRipples - 1; i++) {
206+
if (_shape == M13ProgressViewRadiativeShapeCircle) {
207+
//If circular
208+
UIBezierPath *path = [UIBezierPath bezierPath];
209+
//Calculate the radius
210+
CGFloat radius = _ripplesRadius * ((float)i / (float)(_numberOfRipples - 1));
211+
//Draw the arc
212+
[path moveToPoint:CGPointMake((_originationPoint.x * self.bounds.size.width)+ radius, _originationPoint.y * self.bounds.size.height)];
213+
[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];
214+
//Set the width
215+
path.lineWidth = _rippleWidth;
216+
[ripplePaths addObject:path];
217+
} else if (_shape == M13ProgressViewRadiativeShapeSquare) {
218+
//If square
219+
CGFloat radius = _ripplesRadius * ((float)i / (float)(_numberOfRipples - 1));
220+
CGFloat delta = radius * (1 / sqrtf(2));
221+
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake((_originationPoint.x * self.bounds.size.width) - delta, (_originationPoint.y * self.bounds.size.height) - delta, delta * 2, delta * 2)];
222+
path.lineWidth = _rippleWidth;
223+
[ripplePaths addObject:path];
224+
}
225+
}
226+
}
227+
228+
#pragma mark Drawing
229+
230+
- (void)drawRect:(CGRect)rect
231+
{
232+
[super drawRect:rect];
233+
//Get the current context
234+
CGContextRef context = UIGraphicsGetCurrentContext();
235+
//For each of the paths draw it in the view.
236+
NSEnumerator *enumerator;
237+
if (_progressOutwards) {
238+
enumerator = [ripplePaths objectEnumerator];
239+
} else {
240+
enumerator = [ripplePaths reverseObjectEnumerator];
241+
}
242+
243+
UIBezierPath *path;
244+
int i = 0;
245+
int indexOfLastFilledPath = ceilf(self.progress * (float)_numberOfRipples);
246+
while ((path = [enumerator nextObject])) {
247+
//Set the path's color
248+
if (!self.indeterminate) {
249+
//Show progress
250+
if (i <= indexOfLastFilledPath && self.progress != 0) {
251+
//Highlighted
252+
CGContextSetStrokeColorWithColor(context, self.primaryColor.CGColor);
253+
CGContextAddPath(context, path.CGPath);
254+
CGContextStrokePath(context);
255+
} else {
256+
//Not highlighted
257+
CGContextSetStrokeColorWithColor(context, self.secondaryColor.CGColor);
258+
CGContextAddPath(context, path.CGPath);
259+
CGContextStrokePath(context);
260+
}
261+
i++;
262+
} else {
263+
//Indeterminate
264+
265+
}
266+
}
267+
}
268+
269+
@end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// RadiativeViewController.h
3+
// M13ProgressSuite
4+
//
5+
// Created by Brandon McQuilkin on 3/13/14.
6+
// Copyright (c) 2014 Brandon McQuilkin. All rights reserved.
7+
//
8+
9+
#import <UIKit/UIKit.h>
10+
#import "M13ProgressViewRadiative.h"
11+
12+
@interface RadiativeViewController : UIViewController
13+
14+
@property (nonatomic, retain) IBOutlet M13ProgressViewRadiative *progressView;
15+
@property (nonatomic, retain) IBOutlet UISlider *progressSlider;
16+
@property (nonatomic, retain) IBOutlet UIButton *animateButton;
17+
@property (nonatomic, retain) IBOutlet UISwitch *indeterminateSwitch;
18+
@property (nonatomic, retain) IBOutlet UISegmentedControl *shapeControl;
19+
@property (nonatomic, retain) IBOutlet UISlider *numberOfRipplesSlider;
20+
@property (nonatomic, retain) IBOutlet UISwitch *outwardSwitch;
21+
22+
- (IBAction)animateProgress:(id)sender;
23+
- (IBAction)progressChanged:(id)sender;
24+
- (IBAction)indeterminateChanged:(id)sender;
25+
- (IBAction)shapeChanged:(id)sender;
26+
- (IBAction)numberOfRipplesChanged:(id)sender;
27+
- (IBAction)directionChanged:(id)sender;
28+
29+
30+
@end

0 commit comments

Comments
 (0)