4
// Created by Matej Bukovinski on 2.4.09.
7
#import "MBProgressHUD.h"
9
@interface MBProgressHUD ()
11
- (void)hideUsingAnimation:(BOOL)animated;
12
- (void)showUsingAnimation:(BOOL)animated;
14
- (void)fillRoundedRect:(CGRect)rect inContext:(CGContextRef)context;
18
- (void)updateLabelText:(NSString *)newText;
19
- (void)updateDetailsLabelText:(NSString *)newText;
20
- (void)updateProgress;
21
- (void)updateIndicators;
23
- (void)handleGraceTimer:(NSTimer *)theTimer;
24
- (void)handleMinShowTimer:(NSTimer *)theTimer;
26
@property (retain) UIView *indicator;
28
@property (assign) float width;
29
@property (assign) float height;
31
@property (retain) NSTimer *graceTimer;
32
@property (retain) NSTimer *minShowTimer;
34
@property (retain) NSDate *showStarted;
39
@implementation MBProgressHUD
42
#pragma mark Accessors
47
@synthesize labelText;
48
@synthesize detailsLabelText;
50
@synthesize labelFont;
51
@synthesize detailsLabelFont;
54
@synthesize indicator;
61
@synthesize graceTime;
62
@synthesize minShowTime;
63
@synthesize graceTimer;
64
@synthesize minShowTimer;
65
@synthesize taskInProgress;
67
@synthesize customView;
69
@synthesize showStarted;
71
- (void)setMode:(MBProgressHUDMode)newMode {
72
// Dont change mode if it wasn't actually changed to prevent flickering
73
if (mode && (mode == newMode)) {
79
[self performSelectorOnMainThread:@selector(updateIndicators) withObject:nil waitUntilDone:NO];
80
[self performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
81
[self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
84
- (void)setLabelText:(NSString *)newText {
85
[self performSelectorOnMainThread:@selector(updateLabelText:) withObject:newText waitUntilDone:NO];
86
[self performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
87
[self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
90
- (void)setDetailsLabelText:(NSString *)newText {
91
[self performSelectorOnMainThread:@selector(updateDetailsLabelText:) withObject:newText waitUntilDone:NO];
92
[self performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
93
[self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
96
- (void)setProgress:(float)newProgress {
97
progress = newProgress;
99
// Update display ony if showing the determinate progress view
100
if (mode == MBProgressHUDModeDeterminate) {
101
[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];
102
[self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
107
#pragma mark Accessor helpers
109
- (void)updateLabelText:(NSString *)newText {
110
if (labelText != newText) {
112
labelText = [newText copy];
116
- (void)updateDetailsLabelText:(NSString *)newText {
117
if (detailsLabelText != newText) {
118
[detailsLabelText release];
119
detailsLabelText = [newText copy];
123
- (void)updateProgress {
124
[(MBRoundProgressView *)indicator setProgress:progress];
127
- (void)updateIndicators {
129
[indicator removeFromSuperview];
132
if (mode == MBProgressHUDModeDeterminate) {
133
self.indicator = [[[MBRoundProgressView alloc] initWithDefaultSize] autorelease];
135
else if (mode == MBProgressHUDModeCustomView && self.customView != nil){
136
self.indicator = self.customView;
138
self.indicator = [[[UIActivityIndicatorView alloc]
139
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
140
[(UIActivityIndicatorView *)indicator startAnimating];
144
[self addSubview:indicator];
148
#pragma mark Constants
153
#define LABELFONTSIZE 22.0
154
#define LABELDETAILSFONTSIZE 16.0
156
#define PI 3.14159265358979323846
159
#pragma mark Lifecycle methods
161
- (id)initWithWindow:(UIWindow *)window {
162
return [self initWithView:window];
165
- (id)initWithView:(UIView *)view {
166
// Let's check if the view is nil (this is a common error when using the windw initializer above)
168
[NSException raise:@"MBProgressHUDViewIsNillException"
169
format:@"The view used in the MBProgressHUD initializer is nil."];
171
return [self initWithFrame:view.bounds];
174
- (id)initWithFrame:(CGRect)frame {
175
if (self = [super initWithFrame:frame]) {
176
// Set default values for properties
177
self.mode = MBProgressHUDModeIndeterminate;
178
self.labelText = nil;
179
self.detailsLabelText = nil;
181
self.labelFont = [UIFont boldSystemFontOfSize:LABELFONTSIZE];
182
self.detailsLabelFont = [UIFont boldSystemFontOfSize:LABELDETAILSFONTSIZE];
185
self.graceTime = 0.0;
186
self.minShowTime = 0.0;
188
self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
190
// Transparent background
192
self.backgroundColor = [UIColor clearColor];
194
// Make invisible for now
198
label = [[UILabel alloc] initWithFrame:self.bounds];
201
detailsLabel = [[UILabel alloc] initWithFrame:self.bounds];
211
[detailsLabel release];
213
[detailsLabelText release];
214
[graceTimer release];
215
[minShowTimer release];
216
[showStarted release];
217
[customView release];
224
- (void)layoutSubviews {
225
CGRect frame = self.bounds;
227
// Compute HUD dimensions based on indicator size (add margin to HUD border)
228
CGRect indFrame = indicator.bounds;
229
self.width = indFrame.size.width + 2 * MARGIN;
230
self.height = indFrame.size.height + 2 * MARGIN;
232
// Position the indicator
233
indFrame.origin.x = floor((frame.size.width - indFrame.size.width) / 2) + self.xOffset;
234
indFrame.origin.y = floor((frame.size.height - indFrame.size.height) / 2) + self.yOffset;
235
indicator.frame = indFrame;
237
// Add label if label text was set
238
if (nil != self.labelText) {
239
// Get size of label text
240
CGSize dims = [self.labelText sizeWithFont:self.labelFont];
242
// Compute label dimensions based on font metrics if size is larger than max then clip the label width
243
float lHeight = dims.height;
245
if (dims.width <= (frame.size.width - 2 * MARGIN)) {
249
lWidth = frame.size.width - 4 * MARGIN;
252
// Set label properties
253
label.font = self.labelFont;
254
label.adjustsFontSizeToFitWidth = NO;
255
label.textAlignment = UITextAlignmentCenter;
257
label.backgroundColor = [UIColor clearColor];
258
label.textColor = [UIColor whiteColor];
259
label.text = self.labelText;
262
if (self.width < (lWidth + 2 * MARGIN)) {
263
self.width = lWidth + 2 * MARGIN;
265
self.height = self.height + lHeight + PADDING;
267
// Move indicator to make room for the label
268
indFrame.origin.y -= (floor(lHeight / 2 + PADDING / 2));
269
indicator.frame = indFrame;
271
// Set the label position and dimensions
272
CGRect lFrame = CGRectMake(floor((frame.size.width - lWidth) / 2) + xOffset,
273
floor(indFrame.origin.y + indFrame.size.height + PADDING),
275
label.frame = lFrame;
277
[self addSubview:label];
279
// Add details label delatils text was set
280
if (nil != self.detailsLabelText) {
281
// Get size of label text
282
dims = [self.detailsLabelText sizeWithFont:self.detailsLabelFont];
284
// Compute label dimensions based on font metrics if size is larger than max then clip the label width
285
lHeight = dims.height;
286
if (dims.width <= (frame.size.width - 2 * MARGIN)) {
290
lWidth = frame.size.width - 4 * MARGIN;
293
// Set label properties
294
detailsLabel.font = self.detailsLabelFont;
295
detailsLabel.adjustsFontSizeToFitWidth = NO;
296
detailsLabel.textAlignment = UITextAlignmentCenter;
297
detailsLabel.opaque = NO;
298
detailsLabel.backgroundColor = [UIColor clearColor];
299
detailsLabel.textColor = [UIColor whiteColor];
300
detailsLabel.text = self.detailsLabelText;
303
if (self.width < lWidth) {
304
self.width = lWidth + 2 * MARGIN;
306
self.height = self.height + lHeight + PADDING;
308
// Move indicator to make room for the new label
309
indFrame.origin.y -= (floor(lHeight / 2 + PADDING / 2));
310
indicator.frame = indFrame;
312
// Move first label to make room for the new label
313
lFrame.origin.y -= (floor(lHeight / 2 + PADDING / 2));
314
label.frame = lFrame;
316
// Set label position and dimensions
317
CGRect lFrameD = CGRectMake(floor((frame.size.width - lWidth) / 2) + xOffset,
318
lFrame.origin.y + lFrame.size.height + PADDING, lWidth, lHeight);
319
detailsLabel.frame = lFrameD;
321
[self addSubview:detailsLabel];
327
#pragma mark Showing and execution
329
- (void)show:(BOOL)animated {
330
useAnimation = animated;
332
// If the grace time is set postpone the HUD display
333
if (self.graceTime > 0.0) {
334
self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime
336
selector:@selector(handleGraceTimer:)
340
// ... otherwise show the HUD imediately
342
[self setNeedsDisplay];
343
[self showUsingAnimation:useAnimation];
347
- (void)hide:(BOOL)animated {
348
useAnimation = animated;
350
// If the minShow time is set, calculate how long the hud was shown,
351
// and pospone the hiding operation if necessary
352
if (self.minShowTime > 0.0 && showStarted) {
353
NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted];
354
if (interv < self.minShowTime) {
355
self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv)
357
selector:@selector(handleMinShowTimer:)
364
// ... otherwise hide the HUD immediately
365
[self hideUsingAnimation:useAnimation];
368
- (void)handleGraceTimer:(NSTimer *)theTimer {
369
// Show the HUD only if the task is still running
370
if (taskInProgress) {
371
[self setNeedsDisplay];
372
[self showUsingAnimation:useAnimation];
376
- (void)handleMinShowTimer:(NSTimer *)theTimer {
377
[self hideUsingAnimation:useAnimation];
380
- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {
382
methodForExecution = method;
383
targetForExecution = [target retain];
384
objectForExecution = [object retain];
386
// Launch execution in new thread
387
taskInProgress = YES;
388
[NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil];
391
[self show:animated];
394
- (void)launchExecution {
395
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
397
// Start executing the requested task
398
[targetForExecution performSelector:methodForExecution withObject:objectForExecution];
400
// Task completed, update view in main thread (note: view operations should
401
// be done only in the main thread)
402
[self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];
407
- (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context {
414
// If delegate was set make the callback
417
if(delegate != nil && [delegate conformsToProtocol:@protocol(MBProgressHUDDelegate)]) {
418
if([delegate respondsToSelector:@selector(hudWasHidden)]) {
419
[delegate performSelector:@selector(hudWasHidden)];
427
self.indicator = nil;
429
[targetForExecution release];
430
[objectForExecution release];
432
[self hide:useAnimation];
436
#pragma mark Fade in and Fade out
438
- (void)showUsingAnimation:(BOOL)animated {
439
self.showStarted = [NSDate date];
442
[UIView beginAnimations:nil context:NULL];
443
[UIView setAnimationDuration:0.40];
445
[UIView commitAnimations];
452
- (void)hideUsingAnimation:(BOOL)animated {
455
[UIView beginAnimations:nil context:NULL];
456
[UIView setAnimationDuration:0.40];
457
[UIView setAnimationDelegate:self];
458
[UIView setAnimationDidStopSelector:@selector(animationFinished: finished: context:)];
459
// 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden
460
// in the done method
462
[UIView commitAnimations];
470
#pragma mark BG Drawing
472
- (void)drawRect:(CGRect)rect {
474
CGRect allRect = self.bounds;
475
// Draw rounded HUD bacgroud rect
476
CGRect boxRect = CGRectMake(((allRect.size.width - self.width) / 2) + self.xOffset,
477
((allRect.size.height - self.height) / 2) + self.yOffset, self.width, self.height);
478
CGContextRef ctxt = UIGraphicsGetCurrentContext();
479
[self fillRoundedRect:boxRect inContext:ctxt];
482
- (void)fillRoundedRect:(CGRect)rect inContext:(CGContextRef)context {
483
float radius = 10.0f;
485
CGContextBeginPath(context);
486
CGContextSetGrayFillColor(context, 0.0, self.opacity);
487
CGContextMoveToPoint(context, CGRectGetMinX(rect) + radius, CGRectGetMinY(rect));
488
CGContextAddArc(context, CGRectGetMaxX(rect) - radius, CGRectGetMinY(rect) + radius, radius, 3 * M_PI / 2, 0, 0);
489
CGContextAddArc(context, CGRectGetMaxX(rect) - radius, CGRectGetMaxY(rect) - radius, radius, 0, M_PI / 2, 0);
490
CGContextAddArc(context, CGRectGetMinX(rect) + radius, CGRectGetMaxY(rect) - radius, radius, M_PI / 2, M_PI, 0);
491
CGContextAddArc(context, CGRectGetMinX(rect) + radius, CGRectGetMinY(rect) + radius, radius, M_PI, 3 * M_PI / 2, 0);
492
CGContextClosePath(context);
493
CGContextFillPath(context);
499
@implementation MBRoundProgressView
501
- (id)initWithDefaultSize {
502
return [super initWithFrame:CGRectMake(0.0f, 0.0f, 37.0f, 37.0f)];
505
- (void)drawRect:(CGRect)rect {
506
CGRect allRect = self.bounds;
507
CGRect circleRect = CGRectMake(allRect.origin.x + 2, allRect.origin.y + 2, allRect.size.width - 4,
508
allRect.size.height - 4);
510
CGContextRef context = UIGraphicsGetCurrentContext();
513
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); // white
514
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.1); // translucent white
515
CGContextSetLineWidth(context, 2.0);
516
CGContextFillEllipseInRect(context, circleRect);
517
CGContextStrokeEllipseInRect(context, circleRect);
520
float x = (allRect.size.width / 2);
521
float y = (allRect.size.height / 2);
522
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0); // white
523
CGContextMoveToPoint(context, x, y);
524
CGContextAddArc(context, x, y, (allRect.size.width - 4) / 2, -(PI / 2), (self.progress * 2 * PI) - PI / 2, 0);
525
CGContextClosePath(context);
526
CGContextFillPath(context);