master
  1//
  2// MBProgressHUD.m
  3// Version 0.7
  4// Created by Matej Bukovinski on 2.4.09.
  5//
  6
  7#import "MBProgressHUD.h"
  8
  9
 10#if __has_feature(objc_arc)
 11	#define MB_AUTORELEASE(exp) exp
 12	#define MB_RELEASE(exp) exp
 13	#define MB_RETAIN(exp) exp
 14#else
 15	#define MB_AUTORELEASE(exp) [exp autorelease]
 16	#define MB_RELEASE(exp) [exp release]
 17	#define MB_RETAIN(exp) [exp retain]
 18#endif
 19
 20#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
 21    #define MBLabelAlignmentCenter NSTextAlignmentCenter
 22#else
 23    #define MBLabelAlignmentCenter UITextAlignmentCenter
 24#endif
 25
 26
 27static const CGFloat kPadding = 4.f;
 28static const CGFloat kLabelFontSize = 16.f;
 29static const CGFloat kDetailsLabelFontSize = 12.f;
 30
 31
 32@interface MBProgressHUD ()
 33
 34- (void)setupLabels;
 35- (void)registerForKVO;
 36- (void)unregisterFromKVO;
 37- (NSArray *)observableKeypaths;
 38- (void)registerForNotifications;
 39- (void)unregisterFromNotifications;
 40- (void)updateUIForKeypath:(NSString *)keyPath;
 41- (void)hideUsingAnimation:(BOOL)animated;
 42- (void)showUsingAnimation:(BOOL)animated;
 43- (void)done;
 44- (void)updateIndicators;
 45- (void)handleGraceTimer:(NSTimer *)theTimer;
 46- (void)handleMinShowTimer:(NSTimer *)theTimer;
 47- (void)setTransformForCurrentOrientation:(BOOL)animated;
 48- (void)cleanUp;
 49- (void)launchExecution;
 50- (void)deviceOrientationDidChange:(NSNotification *)notification;
 51- (void)hideDelayed:(NSNumber *)animated;
 52
 53@property (atomic, MB_STRONG) UIView *indicator;
 54@property (atomic, MB_STRONG) NSTimer *graceTimer;
 55@property (atomic, MB_STRONG) NSTimer *minShowTimer;
 56@property (atomic, MB_STRONG) NSDate *showStarted;
 57@property (atomic, assign) CGSize size;
 58
 59@end
 60
 61
 62@implementation MBProgressHUD {
 63	BOOL useAnimation;
 64	SEL methodForExecution;
 65	id targetForExecution;
 66	id objectForExecution;
 67	UILabel *label;
 68	UILabel *detailsLabel;
 69	BOOL isFinished;
 70	CGAffineTransform rotationTransform;
 71}
 72
 73#pragma mark - Properties
 74
 75@synthesize animationType;
 76@synthesize delegate;
 77@synthesize opacity;
 78@synthesize color;
 79@synthesize labelFont;
 80@synthesize detailsLabelFont;
 81@synthesize indicator;
 82@synthesize xOffset;
 83@synthesize yOffset;
 84@synthesize minSize;
 85@synthesize square;
 86@synthesize margin;
 87@synthesize dimBackground;
 88@synthesize graceTime;
 89@synthesize minShowTime;
 90@synthesize graceTimer;
 91@synthesize minShowTimer;
 92@synthesize taskInProgress;
 93@synthesize removeFromSuperViewOnHide;
 94@synthesize customView;
 95@synthesize showStarted;
 96@synthesize mode;
 97@synthesize labelText;
 98@synthesize detailsLabelText;
 99@synthesize progress;
100@synthesize size;
101#if NS_BLOCKS_AVAILABLE
102@synthesize completionBlock;
103#endif
104
105#pragma mark - Class methods
106
107+ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
108	MBProgressHUD *hud = [[self alloc] initWithView:view];
109	[view addSubview:hud];
110	[hud show:animated];
111	return MB_AUTORELEASE(hud);
112}
113
114+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
115	MBProgressHUD *hud = [self HUDForView:view];
116	if (hud != nil) {
117		hud.removeFromSuperViewOnHide = YES;
118		[hud hide:animated];
119		return YES;
120	}
121	return NO;
122}
123
124+ (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated {
125	NSArray *huds = [MBProgressHUD allHUDsForView:view];
126	for (MBProgressHUD *hud in huds) {
127		hud.removeFromSuperViewOnHide = YES;
128		[hud hide:animated];
129	}
130	return [huds count];
131}
132
133+ (MB_INSTANCETYPE)HUDForView:(UIView *)view {
134	NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
135	for (UIView *subview in subviewsEnum) {
136		if ([subview isKindOfClass:self]) {
137			return (MBProgressHUD *)subview;
138		}
139	}
140	return nil;
141}
142
143+ (NSArray *)allHUDsForView:(UIView *)view {
144	NSMutableArray *huds = [NSMutableArray array];
145	NSArray *subviews = view.subviews;
146	for (UIView *aView in subviews) {
147		if ([aView isKindOfClass:self]) {
148			[huds addObject:aView];
149		}
150	}
151	return [NSArray arrayWithArray:huds];
152}
153
154#pragma mark - Lifecycle
155
156- (id)initWithFrame:(CGRect)frame {
157	self = [super initWithFrame:frame];
158	if (self) {
159		// Set default values for properties
160		self.animationType = MBProgressHUDAnimationFade;
161		self.mode = MBProgressHUDModeIndeterminate;
162		self.labelText = nil;
163		self.detailsLabelText = nil;
164		self.opacity = 0.8f;
165        self.color = nil;
166		self.labelFont = [UIFont boldSystemFontOfSize:kLabelFontSize];
167		self.detailsLabelFont = [UIFont boldSystemFontOfSize:kDetailsLabelFontSize];
168		self.xOffset = 0.0f;
169		self.yOffset = 0.0f;
170		self.dimBackground = NO;
171		self.margin = 20.0f;
172		self.graceTime = 0.0f;
173		self.minShowTime = 0.0f;
174		self.removeFromSuperViewOnHide = NO;
175		self.minSize = CGSizeZero;
176		self.square = NO;
177		self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin 
178								| UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
179
180		// Transparent background
181		self.opaque = NO;
182		self.backgroundColor = [UIColor clearColor];
183		// Make it invisible for now
184		self.alpha = 0.0f;
185		
186		taskInProgress = NO;
187		rotationTransform = CGAffineTransformIdentity;
188		
189		[self setupLabels];
190		[self updateIndicators];
191		[self registerForKVO];
192		[self registerForNotifications];
193	}
194	return self;
195}
196
197- (id)initWithView:(UIView *)view {
198	NSAssert(view, @"View must not be nil.");
199	return [self initWithFrame:view.bounds];
200}
201
202- (id)initWithWindow:(UIWindow *)window {
203	return [self initWithView:window];
204}
205
206- (void)dealloc {
207	[self unregisterFromNotifications];
208	[self unregisterFromKVO];
209#if !__has_feature(objc_arc)
210	[color release];
211	[indicator release];
212	[label release];
213	[detailsLabel release];
214	[labelText release];
215	[detailsLabelText release];
216	[graceTimer release];
217	[minShowTimer release];
218	[showStarted release];
219	[customView release];
220#if NS_BLOCKS_AVAILABLE
221	[completionBlock release];
222#endif
223	[super dealloc];
224#endif
225}
226
227#pragma mark - Show & hide
228
229- (void)show:(BOOL)animated {
230	useAnimation = animated;
231	// If the grace time is set postpone the HUD display
232	if (self.graceTime > 0.0) {
233		self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self 
234						   selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
235	} 
236	// ... otherwise show the HUD imediately 
237	else {
238		[self setNeedsDisplay];
239		[self showUsingAnimation:useAnimation];
240	}
241}
242
243- (void)hide:(BOOL)animated {
244	useAnimation = animated;
245	// If the minShow time is set, calculate how long the hud was shown,
246	// and pospone the hiding operation if necessary
247	if (self.minShowTime > 0.0 && showStarted) {
248		NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted];
249		if (interv < self.minShowTime) {
250			self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self 
251								selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
252			return;
253		} 
254	}
255	// ... otherwise hide the HUD immediately
256	[self hideUsingAnimation:useAnimation];
257}
258
259- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay {
260	[self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay];
261}
262
263- (void)hideDelayed:(NSNumber *)animated {
264	[self hide:[animated boolValue]];
265}
266
267#pragma mark - Timer callbacks
268
269- (void)handleGraceTimer:(NSTimer *)theTimer {
270	// Show the HUD only if the task is still running
271	if (taskInProgress) {
272		[self setNeedsDisplay];
273		[self showUsingAnimation:useAnimation];
274	}
275}
276
277- (void)handleMinShowTimer:(NSTimer *)theTimer {
278	[self hideUsingAnimation:useAnimation];
279}
280
281#pragma mark - View Hierrarchy
282
283- (void)didMoveToSuperview {
284	// We need to take care of rotation ourselfs if we're adding the HUD to a window
285	if ([self.superview isKindOfClass:[UIWindow class]]) {
286		[self setTransformForCurrentOrientation:NO];
287	}
288}
289
290#pragma mark - Internal show & hide operations
291
292- (void)showUsingAnimation:(BOOL)animated {
293	if (animated && animationType == MBProgressHUDAnimationZoomIn) {
294		self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
295	} else if (animated && animationType == MBProgressHUDAnimationZoomOut) {
296		self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
297	}
298	self.showStarted = [NSDate date];
299	// Fade in
300	if (animated) {
301		[UIView beginAnimations:nil context:NULL];
302		[UIView setAnimationDuration:0.30];
303		self.alpha = 1.0f;
304		if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) {
305			self.transform = rotationTransform;
306		}
307		[UIView commitAnimations];
308	}
309	else {
310		self.alpha = 1.0f;
311	}
312}
313
314- (void)hideUsingAnimation:(BOOL)animated {
315	// Fade out
316	if (animated && showStarted) {
317		[UIView beginAnimations:nil context:NULL];
318		[UIView setAnimationDuration:0.30];
319		[UIView setAnimationDelegate:self];
320		[UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)];
321		// 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden
322		// in the done method
323		if (animationType == MBProgressHUDAnimationZoomIn) {
324			self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
325		} else if (animationType == MBProgressHUDAnimationZoomOut) {
326			self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
327		}
328
329		self.alpha = 0.02f;
330		[UIView commitAnimations];
331	}
332	else {
333		self.alpha = 0.0f;
334		[self done];
335	}
336	self.showStarted = nil;
337}
338
339- (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context {
340	[self done];
341}
342
343- (void)done {
344	isFinished = YES;
345	self.alpha = 0.0f;
346	if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
347		[delegate performSelector:@selector(hudWasHidden:) withObject:self];
348	}
349#if NS_BLOCKS_AVAILABLE
350	if (self.completionBlock) {
351		self.completionBlock();
352		self.completionBlock = NULL;
353	}
354#endif
355	if (removeFromSuperViewOnHide) {
356		[self removeFromSuperview];
357	}
358}
359
360#pragma mark - Threading
361
362- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {
363	methodForExecution = method;
364	targetForExecution = MB_RETAIN(target);
365	objectForExecution = MB_RETAIN(object);	
366	// Launch execution in new thread
367	self.taskInProgress = YES;
368	[NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil];
369	// Show HUD view
370	[self show:animated];
371}
372
373#if NS_BLOCKS_AVAILABLE
374
375- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block {
376	dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
377	[self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
378}
379
380- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion {
381	dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
382	[self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion];
383}
384
385- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue {
386	[self showAnimated:animated whileExecutingBlock:block onQueue:queue	completionBlock:NULL];
387}
388
389- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue
390	 completionBlock:(MBProgressHUDCompletionBlock)completion {
391	self.taskInProgress = YES;
392	self.completionBlock = completion;
393	dispatch_async(queue, ^(void) {
394        block();
395        dispatch_async(dispatch_get_main_queue(), ^(void) {
396            [self cleanUp];
397        });
398    });
399  [self show:animated];
400}
401
402#endif
403
404- (void)launchExecution {
405	@autoreleasepool {
406#pragma clang diagnostic push
407#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
408		// Start executing the requested task
409		[targetForExecution performSelector:methodForExecution withObject:objectForExecution];
410#pragma clang diagnostic pop
411		// Task completed, update view in main thread (note: view operations should
412		// be done only in the main thread)
413		[self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];
414	}
415}
416
417- (void)cleanUp {
418	taskInProgress = NO;
419	self.indicator = nil;
420#if !__has_feature(objc_arc)
421	[targetForExecution release];
422	[objectForExecution release];
423#else
424	targetForExecution = nil;
425	objectForExecution = nil;
426#endif
427	[self hide:useAnimation];
428}
429
430#pragma mark - UI
431
432- (void)setupLabels {
433	label = [[UILabel alloc] initWithFrame:self.bounds];
434	label.adjustsFontSizeToFitWidth = NO;
435	label.textAlignment = MBLabelAlignmentCenter;
436	label.opaque = NO;
437	label.backgroundColor = [UIColor clearColor];
438	label.textColor = [UIColor whiteColor];
439	label.font = self.labelFont;
440	label.text = self.labelText;
441	[self addSubview:label];
442	
443	detailsLabel = [[UILabel alloc] initWithFrame:self.bounds];
444	detailsLabel.font = self.detailsLabelFont;
445	detailsLabel.adjustsFontSizeToFitWidth = NO;
446	detailsLabel.textAlignment = MBLabelAlignmentCenter;
447	detailsLabel.opaque = NO;
448	detailsLabel.backgroundColor = [UIColor clearColor];
449	detailsLabel.textColor = [UIColor whiteColor];
450	detailsLabel.numberOfLines = 0;
451	detailsLabel.font = self.detailsLabelFont;
452	detailsLabel.text = self.detailsLabelText;
453	[self addSubview:detailsLabel];
454}
455
456- (void)updateIndicators {
457	
458	BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
459	BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
460	
461	if (mode == MBProgressHUDModeIndeterminate &&  !isActivityIndicator) {
462		// Update to indeterminate indicator
463		[indicator removeFromSuperview];
464		self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc]
465										 initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
466		[(UIActivityIndicatorView *)indicator startAnimating];
467		[self addSubview:indicator];
468	}
469	else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
470		// Update to bar determinate indicator
471		[indicator removeFromSuperview];
472        self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]);
473		[self addSubview:indicator];
474	}
475	else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
476		if (!isRoundIndicator) {
477			// Update to determinante indicator
478			[indicator removeFromSuperview];
479			self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]);
480			[self addSubview:indicator];
481		}
482		if (mode == MBProgressHUDModeAnnularDeterminate) {
483			[(MBRoundProgressView *)indicator setAnnular:YES];
484		}
485	} 
486	else if (mode == MBProgressHUDModeCustomView && customView != indicator) {
487		// Update custom view indicator
488		[indicator removeFromSuperview];
489		self.indicator = customView;
490		[self addSubview:indicator];
491	} else if (mode == MBProgressHUDModeText) {
492		[indicator removeFromSuperview];
493		self.indicator = nil;
494	}
495}
496
497#pragma mark - Layout
498
499- (void)layoutSubviews {
500	
501	// Entirely cover the parent view
502	UIView *parent = self.superview;
503	if (parent) {
504		self.frame = parent.bounds;
505	}
506	CGRect bounds = self.bounds;
507	
508	// Determine the total widt and height needed
509	CGFloat maxWidth = bounds.size.width - 4 * margin;
510	CGSize totalSize = CGSizeZero;
511	
512	CGRect indicatorF = indicator.bounds;
513	indicatorF.size.width = MIN(indicatorF.size.width, maxWidth);
514	totalSize.width = MAX(totalSize.width, indicatorF.size.width);
515	totalSize.height += indicatorF.size.height;
516	
517	CGSize labelSize = [label.text sizeWithFont:label.font];
518	labelSize.width = MIN(labelSize.width, maxWidth);
519	totalSize.width = MAX(totalSize.width, labelSize.width);
520	totalSize.height += labelSize.height;
521	if (labelSize.height > 0.f && indicatorF.size.height > 0.f) {
522		totalSize.height += kPadding;
523	}
524
525	CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; 
526	CGSize maxSize = CGSizeMake(maxWidth, remainingHeight);
527	CGSize detailsLabelSize = [detailsLabel.text sizeWithFont:detailsLabel.font 
528								constrainedToSize:maxSize lineBreakMode:detailsLabel.lineBreakMode];
529	totalSize.width = MAX(totalSize.width, detailsLabelSize.width);
530	totalSize.height += detailsLabelSize.height;
531	if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) {
532		totalSize.height += kPadding;
533	}
534	
535	totalSize.width += 2 * margin;
536	totalSize.height += 2 * margin;
537	
538	// Position elements
539	CGFloat yPos = roundf(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset;
540	CGFloat xPos = xOffset;
541	indicatorF.origin.y = yPos;
542	indicatorF.origin.x = roundf((bounds.size.width - indicatorF.size.width) / 2) + xPos;
543	indicator.frame = indicatorF;
544	yPos += indicatorF.size.height;
545	
546	if (labelSize.height > 0.f && indicatorF.size.height > 0.f) {
547		yPos += kPadding;
548	}
549	CGRect labelF;
550	labelF.origin.y = yPos;
551	labelF.origin.x = roundf((bounds.size.width - labelSize.width) / 2) + xPos;
552	labelF.size = labelSize;
553	label.frame = labelF;
554	yPos += labelF.size.height;
555	
556	if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) {
557		yPos += kPadding;
558	}
559	CGRect detailsLabelF;
560	detailsLabelF.origin.y = yPos;
561	detailsLabelF.origin.x = roundf((bounds.size.width - detailsLabelSize.width) / 2) + xPos;
562	detailsLabelF.size = detailsLabelSize;
563	detailsLabel.frame = detailsLabelF;
564	
565	// Enforce minsize and quare rules
566	if (square) {
567		CGFloat max = MAX(totalSize.width, totalSize.height);
568		if (max <= bounds.size.width - 2 * margin) {
569			totalSize.width = max;
570		}
571		if (max <= bounds.size.height - 2 * margin) {
572			totalSize.height = max;
573		}
574	}
575	if (totalSize.width < minSize.width) {
576		totalSize.width = minSize.width;
577	} 
578	if (totalSize.height < minSize.height) {
579		totalSize.height = minSize.height;
580	}
581	
582	self.size = totalSize;
583}
584
585#pragma mark BG Drawing
586
587- (void)drawRect:(CGRect)rect {
588	
589	CGContextRef context = UIGraphicsGetCurrentContext();
590	UIGraphicsPushContext(context);
591
592	if (self.dimBackground) {
593		//Gradient colours
594		size_t gradLocationsNum = 2;
595		CGFloat gradLocations[2] = {0.0f, 1.0f};
596		CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; 
597		CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
598		CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum);
599		CGColorSpaceRelease(colorSpace);
600		//Gradient center
601		CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
602		//Gradient radius
603		float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ;
604		//Gradient draw
605		CGContextDrawRadialGradient (context, gradient, gradCenter,
606									 0, gradCenter, gradRadius,
607									 kCGGradientDrawsAfterEndLocation);
608		CGGradientRelease(gradient);
609	}
610
611    // Set background rect color
612    if (self.color) {
613        CGContextSetFillColorWithColor(context, self.color.CGColor);
614    } else {
615        CGContextSetGrayFillColor(context, 0.0f, self.opacity);
616    }
617
618	
619	// Center HUD
620	CGRect allRect = self.bounds;
621	// Draw rounded HUD backgroud rect
622	CGRect boxRect = CGRectMake(roundf((allRect.size.width - size.width) / 2) + self.xOffset,
623								roundf((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height);
624	float radius = 10.0f;
625	CGContextBeginPath(context);
626	CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect));
627	CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0);
628	CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0);
629	CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0);
630	CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0);
631	CGContextClosePath(context);
632	CGContextFillPath(context);
633
634	UIGraphicsPopContext();
635}
636
637#pragma mark - KVO
638
639- (void)registerForKVO {
640	for (NSString *keyPath in [self observableKeypaths]) {
641		[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
642	}
643}
644
645- (void)unregisterFromKVO {
646	for (NSString *keyPath in [self observableKeypaths]) {
647		[self removeObserver:self forKeyPath:keyPath];
648	}
649}
650
651- (NSArray *)observableKeypaths {
652	return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", 
653			@"detailsLabelText", @"detailsLabelFont", @"progress", nil];
654}
655
656- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
657	if (![NSThread isMainThread]) {
658		[self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO];
659	} else {
660		[self updateUIForKeypath:keyPath];
661	}
662}
663
664- (void)updateUIForKeypath:(NSString *)keyPath {
665	if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"]) {
666		[self updateIndicators];
667	} else if ([keyPath isEqualToString:@"labelText"]) {
668		label.text = self.labelText;
669	} else if ([keyPath isEqualToString:@"labelFont"]) {
670		label.font = self.labelFont;
671	} else if ([keyPath isEqualToString:@"detailsLabelText"]) {
672		detailsLabel.text = self.detailsLabelText;
673	} else if ([keyPath isEqualToString:@"detailsLabelFont"]) {
674		detailsLabel.font = self.detailsLabelFont;
675	} else if ([keyPath isEqualToString:@"progress"]) {
676		if ([indicator respondsToSelector:@selector(setProgress:)]) {
677			[(id)indicator setProgress:progress];
678		}
679		return;
680	}
681	[self setNeedsLayout];
682	[self setNeedsDisplay];
683}
684
685#pragma mark - Notifications
686
687- (void)registerForNotifications {
688	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
689	[nc addObserver:self selector:@selector(deviceOrientationDidChange:) 
690			   name:UIDeviceOrientationDidChangeNotification object:nil];
691}
692
693- (void)unregisterFromNotifications {
694	[[NSNotificationCenter defaultCenter] removeObserver:self];
695}
696
697- (void)deviceOrientationDidChange:(NSNotification *)notification { 
698	UIView *superview = self.superview;
699	if (!superview) {
700		return;
701	} else if ([superview isKindOfClass:[UIWindow class]]) {
702		[self setTransformForCurrentOrientation:YES];
703	} else {
704		self.bounds = self.superview.bounds;
705		[self setNeedsDisplay];
706	}
707}
708
709- (void)setTransformForCurrentOrientation:(BOOL)animated {	
710	// Stay in sync with the superview
711	if (self.superview) {
712		self.bounds = self.superview.bounds;
713		[self setNeedsDisplay];
714	}
715	
716	UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
717	CGFloat radians = 0;
718	if (UIInterfaceOrientationIsLandscape(orientation)) {
719		if (orientation == UIInterfaceOrientationLandscapeLeft) { radians = -(CGFloat)M_PI_2; } 
720		else { radians = (CGFloat)M_PI_2; }
721		// Window coordinates differ!
722		self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width);
723	} else {
724		if (orientation == UIInterfaceOrientationPortraitUpsideDown) { radians = (CGFloat)M_PI; } 
725		else { radians = 0; }
726	}
727	rotationTransform = CGAffineTransformMakeRotation(radians);
728	
729	if (animated) {
730		[UIView beginAnimations:nil context:nil];
731	}
732	[self setTransform:rotationTransform];
733	if (animated) {
734		[UIView commitAnimations];
735	}
736}
737
738@end
739
740
741@implementation MBRoundProgressView
742
743#pragma mark - Lifecycle
744
745- (id)init {
746	return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)];
747}
748
749- (id)initWithFrame:(CGRect)frame {
750	self = [super initWithFrame:frame];
751	if (self) {
752		self.backgroundColor = [UIColor clearColor];
753		self.opaque = NO;
754		_progress = 0.f;
755		_annular = NO;
756		_progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
757		_backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f];
758		[self registerForKVO];
759	}
760	return self;
761}
762
763- (void)dealloc {
764	[self unregisterFromKVO];
765#if !__has_feature(objc_arc)
766	[_progressTintColor release];
767	[_backgroundTintColor release];
768	[super dealloc];
769#endif
770}
771
772#pragma mark - Drawing
773
774- (void)drawRect:(CGRect)rect {
775	
776	CGRect allRect = self.bounds;
777	CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f);
778	CGContextRef context = UIGraphicsGetCurrentContext();
779	
780	if (_annular) {
781		// Draw background
782		CGFloat lineWidth = 5.f;
783		UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
784		processBackgroundPath.lineWidth = lineWidth;
785		processBackgroundPath.lineCapStyle = kCGLineCapRound;
786		CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
787		CGFloat radius = (self.bounds.size.width - lineWidth)/2;
788		CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
789		CGFloat endAngle = (2 * (float)M_PI) + startAngle;
790		[processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
791		[_backgroundTintColor set];
792		[processBackgroundPath stroke];
793		// Draw progress
794		UIBezierPath *processPath = [UIBezierPath bezierPath];
795		processPath.lineCapStyle = kCGLineCapRound;
796		processPath.lineWidth = lineWidth;
797		endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
798		[processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
799		[_progressTintColor set];
800		[processPath stroke];
801	} else {
802		// Draw background
803		[_progressTintColor setStroke];
804		[_backgroundTintColor setFill];
805		CGContextSetLineWidth(context, 2.0f);
806		CGContextFillEllipseInRect(context, circleRect);
807		CGContextStrokeEllipseInRect(context, circleRect);
808		// Draw progress
809		CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
810		CGFloat radius = (allRect.size.width - 4) / 2;
811		CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
812		CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
813		CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white
814		CGContextMoveToPoint(context, center.x, center.y);
815		CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
816		CGContextClosePath(context);
817		CGContextFillPath(context);
818	}
819}
820
821#pragma mark - KVO
822
823- (void)registerForKVO {
824	for (NSString *keyPath in [self observableKeypaths]) {
825		[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
826	}
827}
828
829- (void)unregisterFromKVO {
830	for (NSString *keyPath in [self observableKeypaths]) {
831		[self removeObserver:self forKeyPath:keyPath];
832	}
833}
834
835- (NSArray *)observableKeypaths {
836	return [NSArray arrayWithObjects:@"progressTintColor", @"backgroundTintColor", @"progress", @"annular", nil];
837}
838
839- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
840	[self setNeedsDisplay];
841}
842
843@end
844
845
846@implementation MBBarProgressView
847
848#pragma mark - Lifecycle
849
850- (id)init {
851	return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)];
852}
853
854- (id)initWithFrame:(CGRect)frame {
855    self = [super initWithFrame:frame];
856    if (self) {
857		_progress = 0.f;
858		_lineColor = [UIColor whiteColor];
859		_progressColor = [UIColor whiteColor];
860		_progressRemainingColor = [UIColor clearColor];
861		self.backgroundColor = [UIColor clearColor];
862		self.opaque = NO;
863		[self registerForKVO];
864    }
865    return self;
866}
867
868- (void)dealloc {
869	[self unregisterFromKVO];
870#if !__has_feature(objc_arc)
871	[_lineColor release];
872	[_progressColor release];
873	[_progressRemainingColor release];
874	[super dealloc];
875#endif
876}
877
878#pragma mark - Drawing
879
880- (void)drawRect:(CGRect)rect {
881	CGContextRef context = UIGraphicsGetCurrentContext();
882	
883	// setup properties
884	CGContextSetLineWidth(context, 2);
885	CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
886	CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);
887	
888	// draw line border
889	float radius = (rect.size.height / 2) - 2;
890	CGContextMoveToPoint(context, 2, rect.size.height/2);
891	CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
892	CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2);
893	CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
894	CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
895	CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2);
896	CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
897	CGContextFillPath(context);
898	
899	// draw progress background
900	CGContextMoveToPoint(context, 2, rect.size.height/2);
901	CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
902	CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2);
903	CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
904	CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
905	CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2);
906	CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
907	CGContextStrokePath(context);
908	
909	// setup to draw progress color
910	CGContextSetFillColorWithColor(context, [_progressColor CGColor]);
911	radius = radius - 2;
912	float amount = self.progress * rect.size.width;
913	
914	// if progress is in the middle area
915	if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) {
916		// top
917		CGContextMoveToPoint(context, 4, rect.size.height/2);
918		CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
919		CGContextAddLineToPoint(context, amount, 4);
920		CGContextAddLineToPoint(context, amount, radius + 4);
921		
922		// bottom
923		CGContextMoveToPoint(context, 4, rect.size.height/2);
924		CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
925		CGContextAddLineToPoint(context, amount, rect.size.height - 4);
926		CGContextAddLineToPoint(context, amount, radius + 4);
927		
928		CGContextFillPath(context);
929	}
930	
931	// progress is in the right arc
932	else if (amount > radius + 4) {
933		float x = amount - (rect.size.width - radius - 4);
934		
935		// top
936		CGContextMoveToPoint(context, 4, rect.size.height/2);
937		CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
938		CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4);
939		float angle = -acos(x/radius);
940		if (isnan(angle)) angle = 0;
941		CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0);
942		CGContextAddLineToPoint(context, amount, rect.size.height/2);
943		
944		// bottom
945		CGContextMoveToPoint(context, 4, rect.size.height/2);
946		CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
947		CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4);
948		angle = acos(x/radius);
949		if (isnan(angle)) angle = 0;
950		CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1);
951		CGContextAddLineToPoint(context, amount, rect.size.height/2);
952		
953		CGContextFillPath(context);
954	}
955	
956	// progress is in the left arc
957	else if (amount < radius + 4 && amount > 0) {
958		// top
959		CGContextMoveToPoint(context, 4, rect.size.height/2);
960		CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
961		CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
962		
963		// bottom
964		CGContextMoveToPoint(context, 4, rect.size.height/2);
965		CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
966		CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
967		
968		CGContextFillPath(context);
969	}
970}
971
972#pragma mark - KVO
973
974- (void)registerForKVO {
975	for (NSString *keyPath in [self observableKeypaths]) {
976		[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
977	}
978}
979
980- (void)unregisterFromKVO {
981	for (NSString *keyPath in [self observableKeypaths]) {
982		[self removeObserver:self forKeyPath:keyPath];
983	}
984}
985
986- (NSArray *)observableKeypaths {
987	return [NSArray arrayWithObjects:@"lineColor", @"progressRemainingColor", @"progressColor", @"progress", nil];
988}
989
990- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
991	[self setNeedsDisplay];
992}
993
994@end