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