From 4af17598a5aee9444e5d6e46b95119da272e8e9a Mon Sep 17 00:00:00 2001 From: Matej Bukovinski Date: Thu, 22 Mar 2012 15:41:55 +0100 Subject: [PATCH] Major code rewrite / cleanup. closes #56 #60 - MBProgressHUD now requires LLVM 3+ - instance variables moved to the class implementation or removed (using synthesized instance vars) - using KVO instead of custom accessors - completely rewritten layout and UI buildup code - more concise arc conditionals - added MBProgressHUDModeText (+ example) - various documentation fixes - migrated to @autoreleasepool - general source code cleanup and restructuring - removed deprecated delegate callback - version bump to 5.0 --- Demo/Classes/HudDemoViewController.m | 14 + Demo/en.lproj/HudDemoViewController.xib | 49 +- MBProgressHUD.h | 291 ++++---- MBProgressHUD.m | 843 +++++++++++------------- README.mdown | 18 +- 5 files changed, 557 insertions(+), 658 deletions(-) diff --git a/Demo/Classes/HudDemoViewController.m b/Demo/Classes/HudDemoViewController.m index 3a54be67e..df1971635 100644 --- a/Demo/Classes/HudDemoViewController.m +++ b/Demo/Classes/HudDemoViewController.m @@ -188,6 +188,20 @@ - (IBAction)showWithGradient:(id)sender { [HUD showWhileExecuting:@selector(myTask) onTarget:self withObject:nil animated:YES]; } +- (IBAction)showTextOnly:(id)sender { + + MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES]; + + // Configure for text only and offset down + hud.mode = MBProgressHUDModeText; + hud.labelText = @"Some message..."; + hud.margin = 10.f; + hud.yOffset = 150.f; + hud.removeFromSuperViewOnHide = YES; + + [hud hide:YES afterDelay:3]; +} + #pragma mark - #pragma mark Execution code diff --git a/Demo/en.lproj/HudDemoViewController.xib b/Demo/en.lproj/HudDemoViewController.xib index 44847e713..77216a43c 100644 --- a/Demo/en.lproj/HudDemoViewController.xib +++ b/Demo/en.lproj/HudDemoViewController.xib @@ -299,13 +299,37 @@ {{20, 499}, {280, 40}} + NO NO IBCocoaTouchFramework 0 3 1 - With Gradient + Dim background + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + + + 294 + {{20, 547}, {280, 40}} + + + + NO + NO + IBCocoaTouchFramework + 0 + 3 + 1 + Text only 1 @@ -316,7 +340,7 @@ - {320, 546} + {320, 607} @@ -329,7 +353,7 @@ IBCocoaTouchFramework - {320, 546} + {320, 607} @@ -448,6 +472,15 @@ 108 + + + showTextOnly: + + + 7 + + 113 + @@ -496,6 +529,7 @@ + @@ -554,6 +588,11 @@ + + 111 + + + @@ -566,6 +605,7 @@ -2.IBPluginDependency 10.IBPluginDependency 106.IBPluginDependency + 111.IBPluginDependency 16.IBPluginDependency 20.IBPluginDependency 43.IBPluginDependency @@ -597,6 +637,7 @@ com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -611,7 +652,7 @@ - 110 + 113 diff --git a/MBProgressHUD.h b/MBProgressHUD.h index 4ee195791..63a43cffe 100755 --- a/MBProgressHUD.h +++ b/MBProgressHUD.h @@ -1,6 +1,6 @@ // // MBProgressHUD.h -// Version 0.4 +// Version 0.5 // Created by Matej Bukovinski on 2.4.09. // @@ -32,16 +32,18 @@ @protocol MBProgressHUDDelegate; -///////////////////////////////////////////////////////////////////////////////////////////// typedef enum { /** Progress is shown using an UIActivityIndicatorView. This is the default. */ MBProgressHUDModeIndeterminate, - /** Progress is shown using a MBRoundProgressView. */ + /** Progress is shown using a round, pie-chart like, progress view. */ MBProgressHUDModeDeterminate, + /** Progress is shown using a ring-shaped progress view. */ MBProgressHUDModeAnnularDeterminate, /** Shows a custom view */ - MBProgressHUDModeCustomView + MBProgressHUDModeCustomView, + /** Shows only labels */ + MBProgressHUDModeText } MBProgressHUDMode; typedef enum { @@ -51,7 +53,25 @@ typedef enum { MBProgressHUDAnimationZoom } MBProgressHUDAnimation; -///////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef MB_STRONG +#if __has_feature(objc_arc) + #define MB_STRONG strong +#else + #define MB_STRONG retain +#endif +#endif + +#ifndef MB_WEAK +#if __has_feature(objc_arc_weak) + #define MB_WEAK weak +#elif __has_feature(objc_arc) + #define MB_WEAK unsafe_unretained +#else + #define MB_WEAK assign +#endif +#endif + /** * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. @@ -61,9 +81,10 @@ typedef enum { * user input on this region, thereby preventing the user operations on components below the view. The HUD itself is * drawn centered as a rounded semi-transparent view witch resizes depending on the user specified content. * - * This view supports three modes of operation: + * This view supports four modes of operation: * - MBProgressHUDModeIndeterminate - shows a UIActivityIndicatorView - * - MBProgressHUDModeDeterminate - shows a custom round progress indicator (MBRoundProgressView) + * - MBProgressHUDModeDeterminate - shows a custom round progress indicator + * - MBProgressHUDModeAnnularDeterminate - shows a custom annular progress indicator * - MBProgressHUDModeCustomView - shows an arbitrary, user specified view (@see customView) * * All three modes can have optional labels assigned: @@ -71,62 +92,7 @@ typedef enum { * indicator view. * - If also the detailsLabelText property is set then another label is placed below the first label. */ -@interface MBProgressHUD : UIView { - - MBProgressHUDMode mode; - MBProgressHUDAnimation animationType; - - SEL methodForExecution; - id targetForExecution; - id objectForExecution; - BOOL useAnimation; - - float yOffset; - float xOffset; - - float width; - float height; - - CGSize minSize; - BOOL square; - - float margin; - - BOOL dimBackground; - - BOOL taskInProgress; - float graceTime; - float minShowTime; - NSTimer *graceTimer; - NSTimer *minShowTimer; - NSDate *showStarted; - - UIView *indicator; - UILabel *label; - UILabel *detailsLabel; - - float progress; - -#if __has_feature(objc_arc_weak) - id __weak delegate; -#elif __has_feature(objc_arc) - id __unsafe_unretained delegate; -#else - id delegate; -#endif - NSString *labelText; - NSString *detailsLabelText; - float opacity; - UIFont *labelFont; - UIFont *detailsLabelFont; - - BOOL isFinished; - BOOL removeFromSuperViewOnHide; - - UIView *customView; - - CGAffineTransform rotationTransform; -} +@interface MBProgressHUD : UIView /** * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. @@ -137,11 +103,12 @@ typedef enum { * @return A reference to the created HUD. * * @see hideHUDForView:animated: + * @see animationType */ + (MBProgressHUD *)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; /** - * Finds a HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. + * Finds the tompost HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. * * @param view The view that is going to be searched for a HUD subview. * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use @@ -149,46 +116,93 @@ typedef enum { * @return YES if a HUD was found and removed, NO otherwise. * * @see showHUDAddedTo:animated: + * @see animationType */ + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; /** - * Finds all the HUD subviews and hides them. The counterpart to this method is showHUDAddedTo:animated:. + * Finds all the HUD subviews and hides them. * * @param view The view that is going to be searched for HUD subviews. - * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * @param animated If set to YES the HUDs will disappear using the current animationType. If set to NO the HUDs will not use * animations while disappearing. - * @return the number of HUD found in the subviews and removed. + * @return the number of HUDs found and removed. * * @see hideAllHUDForView:animated: + * @see animationType */ + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated; /** - * Finds a HUD subview and returns it. This is used internally by hideHUDForFiew:animated:, but can also be useful externally. + * Finds the topomost HUD subview and returns it. * - * @param view The view that is going to be searched for a HUD subview. + * @param view The view that is going to be searched. * @return A reference to the last HUD subview discovered. - * - * @see hideHUDForView:animated: */ + (MBProgressHUD *)HUDForView:(UIView *)view; /** - * Finds all HUD subviews and returns them. This is used internally by hideAllHUDsForView:animated:, but can also be useful externally. + * Finds all HUD subviews and returns them. * - * @param view The view that is going to be searched for HUD subviews. + * @param view The view that is going to be searched. * @return All found HUD views (array of MBProgressHUD objects). - * - * @see hideAllHUDsForView:animated: */ + (NSArray *)allHUDsForView:(UIView *)view; +/** + * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so + * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread + * (e.g., when using something like NSOperation or calling an asynchronous call like NSUrlRequest). + * + * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * animations while disappearing. + * + * @see animationType + */ +- (void)show:(BOOL)animated; + +/** + * Hide the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the hide: method. Use it to + * hide the HUD when your task completes. + * + * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * animations while disappearing. + * + * @see animationType + */ +- (void)hide:(BOOL)animated; + +/** + * Hide the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the hide: method. Use it to + * hide the HUD when your task completes. + * + * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * animations while disappearing. + * @param delay Delay in secons until the HUD is hidden. + * + * @see animationType + */ +- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; + +/** + * Shows the HUD while a background task is executing in a new thread, then hides the HUD. + * + * This method also takes care of NSAutoreleasePools so your method does not have to be concerned with setting up a + * pool. + * + * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. + * @param target The object that the target method belongs to. + * @param object An optional object to be passed to the method. + * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use + * animations while disappearing. + */ +- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; + /** * A convenience constructor that initializes the HUD with the window's bounds. Calls the designated constructor with * window.bounds as the parameter. * - * @param window The window instance that will provide the bounds for the HUD. Should probably be the same instance as + * @param window The window instance that will provide the bounds for the HUD. Should be the same instance as * the HUD's superview (i.e., the window that the HUD will be added to). */ - (id)initWithWindow:(UIWindow *)window; @@ -197,24 +211,13 @@ typedef enum { * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with * view.bounds as the parameter * - * @param view The view instance that will provide the bounds for the HUD. Should probably be the same instance as + * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as * the HUD's superview (i.e., the view that the HUD will be added to). */ - (id)initWithView:(UIView *)view; -/** - * The UIView (i.g., a UIIMageView) to be shown when the HUD is in MBProgressHUDModeCustomView. - * For best results use a 37 by 37 pixel view (so the bounds match the build in indicator bounds). - */ -#if __has_feature(objc_arc) -@property (strong) UIView *customView; -#else -@property (retain) UIView *customView; -#endif - /** - * MBProgressHUD operation mode. Switches between indeterminate (MBProgressHUDModeIndeterminate) and determinate - * progress (MBProgressHUDModeDeterminate). The default is MBProgressHUDModeIndeterminate. + * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. * * @see MBProgressHUDMode */ @@ -227,18 +230,18 @@ typedef enum { */ @property (assign) MBProgressHUDAnimation animationType; +/** + * The UIView (e.g., a UIIMageView) to be shown when the HUD is in MBProgressHUDModeCustomView. + * For best results use a 37 by 37 pixel view (so the bounds match the build in indicator bounds). + */ +@property (MB_STRONG) UIView *customView; + /** - * The HUD delegate object. If set the delegate will receive hudWasHidden callbacks when the HUD was hidden. The - * delegate should conform to the MBProgressHUDDelegate protocol and implement the hudWasHidden method. The delegate - * object will not be retained. + * The HUD delegate object. + * + * @see MBProgressHUDDelegate */ -#if __has_feature(objc_arc_weak) -@property (weak) id delegate; -#elif __has_feature(objc_arc) -@property (unsafe_unretained) id delegate; -#else -@property (assign) id delegate; -#endif +@property (MB_WEAK) id delegate; /** * An optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit @@ -249,7 +252,7 @@ typedef enum { /** * An optional details message displayed below the labelText message. This message is displayed only if the labelText - * property is also set and is different from an empty string (@""). + * property is also set and is different from an empty string (@""). The details text can span multiple lines. */ @property (copy) NSString *detailsLabelText; @@ -269,8 +272,7 @@ typedef enum { @property (assign) float yOffset; /** - * The amounth of space between the HUD edge and the HUD elements (labels, indicators or custom views). - * + * The amounth of space between the HUD edge and the HUD elements (labels, indicators or custom views). * Defaults to 20.0 */ @property (assign) float margin; @@ -291,7 +293,6 @@ typedef enum { */ @property (assign) float graceTime; - /** * The minimum time (in seconds) that the HUD is shown. * This avoids the problem of the HUD being shown and than instantly hidden. @@ -318,20 +319,12 @@ typedef enum { /** * Font to be used for the main label. Set this property if the default is not adequate. */ -#if __has_feature(objc_arc) -@property (strong) UIFont* labelFont; -#else -@property (retain) UIFont* labelFont; -#endif +@property (MB_STRONG) UIFont* labelFont; /** * Font to be used for the details label. Set this property if the default is not adequate. */ -#if __has_feature(objc_arc) -@property (strong) UIFont* detailsLabelFont; -#else -@property (retain) UIFont* detailsLabelFont; -#endif +@property (MB_STRONG) UIFont* detailsLabelFont; /** * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. @@ -339,7 +332,7 @@ typedef enum { @property (assign) float progress; /** - * The minimum size of the HUD bezel. Defaults to CGSizeZero. + * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). */ @property (assign) CGSize minSize; @@ -348,57 +341,8 @@ typedef enum { */ @property (assign, getter = isSquare) BOOL square; -/** - * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so - * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread - * (e.g., when using something like NSOperation or calling an asynchronous call like NSUrlRequest). - * - * If you need to perform a blocking thask on the main thread, you can try spining the run loop imeidiately after calling this - * method by using: - * - * [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantPast]]; - * - * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use - * animations while disappearing. - */ -- (void)show:(BOOL)animated; - -/** - * Hide the HUD. This still calls the hudWasHidden delegate. This is the counterpart of the hide: method. Use it to - * hide the HUD when your task completes. - * - * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use - * animations while disappearing. - */ -- (void)hide:(BOOL)animated; - -/** - * Hide the HUD after a delay. This still calls the hudWasHidden delegate. This is the counterpart of the hide: method. Use it to - * hide the HUD when your task completes. - * - * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use - * animations while disappearing. - * @param delay Delay in secons until the HUD is hidden. - */ -- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; - -/** - * Shows the HUD while a background task is executing in a new thread, then hides the HUD. - * - * This method also takes care of NSAutoreleasePools so your method does not have to be concerned with setting up a - * pool. - * - * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. - * @param target The object that the target method belongs to. - * @param object An optional object to be passed to the method. - * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use - * animations while disappearing. - */ -- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; - @end -///////////////////////////////////////////////////////////////////////////////////////////// @protocol MBProgressHUDDelegate @@ -409,33 +353,22 @@ typedef enum { */ - (void)hudWasHidden:(MBProgressHUD *)hud; -/** - * @deprecated use hudWasHidden: instead - * @see hudWasHidden: - */ -- (void)hudWasHidden __attribute__ ((deprecated)); - @end -///////////////////////////////////////////////////////////////////////////////////////////// /** * A progress view for showing definite progress by filling up a circle (pie chart). */ -@interface MBRoundProgressView : UIView { -@private - float _progress; - BOOL _isAnnular; -} +@interface MBRoundProgressView : UIView /** * Progress (0.0 to 1.0) */ @property (nonatomic, assign) float progress; -@property (nonatomic, assign) BOOL isAnnular; +/* + * Display mode - NO = round or YES = annular. Defaults to round. + */ +@property (nonatomic, assign, getter = isAnnular) BOOL annular; @end - -///////////////////////////////////////////////////////////////////////////////////////////// - diff --git a/MBProgressHUD.m b/MBProgressHUD.m index 9a512ec9d..f4168add7 100755 --- a/MBProgressHUD.m +++ b/MBProgressHUD.m @@ -1,19 +1,40 @@ // // MBProgressHUD.m -// Version 0.4 +// Version 0.5 // Created by Matej Bukovinski on 2.4.09. // #import "MBProgressHUD.h" + +#if __has_feature(objc_arc) + #define MB_AUTORELEASE(exp) exp + #define MB_RELEASE(exp) exp + #define MB_RETAIN(exp) exp +#else + #define MB_AUTORELEASE(exp) [exp autorelease] + #define MB_RELEASE(exp) [exp release] + #define MB_RETAIN(exp) [exp retain] +#endif + + +static const CGFloat kPadding = 4.f; +static const CGFloat kLabelFontSize = 16.f; +static const CGFloat kDetailsLabelFontSize = 12.f; + + @interface MBProgressHUD () +- (void)setupLabels; +- (void)registerForKVO; +- (void)unregisterFromKVO; +- (NSArray *)observableKeypaths; +- (void)registerForNotifications; +- (void)unregisterFromNotifications; +- (void)updateUIForKeypath:(NSString *)keyPath; - (void)hideUsingAnimation:(BOOL)animated; - (void)showUsingAnimation:(BOOL)animated; - (void)done; -- (void)updateLabelText:(NSString *)newText; -- (void)updateDetailsLabelText:(NSString *)newText; -- (void)updateProgress; - (void)updateIndicators; - (void)handleGraceTimer:(NSTimer *)theTimer; - (void)handleMinShowTimer:(NSTimer *)theTimer; @@ -23,209 +44,61 @@ - (void)launchExecution; - (void)deviceOrientationDidChange:(NSNotification *)notification; - (void)hideDelayed:(NSNumber *)animated; -#if __has_feature(objc_arc) -@property (strong) UIView *indicator; -@property (strong) NSTimer *graceTimer; -@property (strong) NSTimer *minShowTimer; -@property (strong) NSDate *showStarted; -#else -@property (retain) UIView *indicator; -@property (retain) NSTimer *graceTimer; -@property (retain) NSTimer *minShowTimer; -@property (retain) NSDate *showStarted; -#endif - -@property (assign) float width; -@property (assign) float height; +@property (MB_STRONG) UIView *indicator; +@property (MB_STRONG) NSTimer *graceTimer; +@property (MB_STRONG) NSTimer *minShowTimer; +@property (MB_STRONG) NSDate *showStarted; +@property (assign) CGSize size; @end -@implementation MBProgressHUD +@implementation MBProgressHUD { + BOOL useAnimation; + SEL methodForExecution; + id targetForExecution; + id objectForExecution; + UILabel *label; + UILabel *detailsLabel; + BOOL isFinished; + CGAffineTransform rotationTransform; +} -#pragma mark - -#pragma mark Accessors +#pragma mark - Properties @synthesize animationType; - @synthesize delegate; @synthesize opacity; @synthesize labelFont; @synthesize detailsLabelFont; - @synthesize indicator; - -@synthesize width; -@synthesize height; @synthesize xOffset; @synthesize yOffset; @synthesize minSize; @synthesize square; @synthesize margin; @synthesize dimBackground; - @synthesize graceTime; @synthesize minShowTime; @synthesize graceTimer; @synthesize minShowTimer; @synthesize taskInProgress; @synthesize removeFromSuperViewOnHide; - @synthesize customView; - @synthesize showStarted; +@synthesize mode; +@synthesize labelText; +@synthesize detailsLabelText; +@synthesize progress; +@synthesize size; -- (void)setMode:(MBProgressHUDMode)newMode { - // Dont change mode if it wasn't actually changed to prevent flickering - if (mode && (mode == newMode)) { - return; - } - - mode = newMode; - - if ([NSThread isMainThread]) { - [self updateIndicators]; - [self setNeedsLayout]; - [self setNeedsDisplay]; - } else { - [self performSelectorOnMainThread:@selector(updateIndicators) withObject:nil waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO]; - } -} - -- (MBProgressHUDMode)mode { - return mode; -} - -- (void)setLabelText:(NSString *)newText { - if ([NSThread isMainThread]) { - [self updateLabelText:newText]; - [self setNeedsLayout]; - [self setNeedsDisplay]; - } else { - [self performSelectorOnMainThread:@selector(updateLabelText:) withObject:newText waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO]; - } -} - -- (NSString *)labelText { - return labelText; -} - -- (void)setDetailsLabelText:(NSString *)newText { - if ([NSThread isMainThread]) { - [self updateDetailsLabelText:newText]; - [self setNeedsLayout]; - [self setNeedsDisplay]; - } else { - [self performSelectorOnMainThread:@selector(updateDetailsLabelText:) withObject:newText waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO]; - } -} - -- (NSString *)detailsLabelText { - return detailsLabelText; -} - -- (void)setProgress:(float)newProgress { - progress = newProgress; - - // Update display ony if showing the determinate progress view - if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { - if ([NSThread isMainThread]) { - [self updateProgress]; - [self setNeedsDisplay]; - } else { - [self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO]; - } - } -} - -- (float)progress { - return progress; -} - -#pragma mark - -#pragma mark Accessor helpers - -- (void)updateLabelText:(NSString *)newText { - if (labelText != newText) { -#if !__has_feature(objc_arc) - [labelText release]; -#endif - labelText = [newText copy]; - } -} - -- (void)updateDetailsLabelText:(NSString *)newText { - if (detailsLabelText != newText) { -#if !__has_feature(objc_arc) - [detailsLabelText release]; -#endif - detailsLabelText = [newText copy]; - } -} - -- (void)updateProgress { - [(MBRoundProgressView *)indicator setProgress:progress]; -} - -- (void)updateIndicators { - if (indicator) { - [indicator removeFromSuperview]; - } - - if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { -#if __has_feature(objc_arc) - self.indicator = [[MBRoundProgressView alloc] init]; -#else - self.indicator = [[[MBRoundProgressView alloc] init] autorelease]; -#endif - if (mode == MBProgressHUDModeAnnularDeterminate) { - [(MBRoundProgressView *)self.indicator setIsAnnular:YES]; - } - } - else if (mode == MBProgressHUDModeCustomView && self.customView != nil){ - self.indicator = self.customView; - } else { -#if __has_feature(objc_arc) - self.indicator = [[UIActivityIndicatorView alloc] - initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; -#else - self.indicator = [[[UIActivityIndicatorView alloc] - initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease]; -#endif - [(UIActivityIndicatorView *)indicator startAnimating]; - } - - - [self addSubview:indicator]; -} - -#pragma mark - -#pragma mark Constants - -#define PADDING 4.0f - -#define LABELFONTSIZE 16.0f -#define LABELDETAILSFONTSIZE 12.0f - -#pragma mark - -#pragma mark Class methods +#pragma mark - Class methods + (MBProgressHUD *)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:view]; [view addSubview:hud]; [hud show:animated]; -#if __has_feature(objc_arc) - return hud; -#else - return [hud autorelease]; -#endif + return MB_AUTORELEASE(hud); } + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { @@ -271,38 +144,7 @@ + (NSArray *)allHUDsForView:(UIView *)view { return [NSArray arrayWithArray:huds]; } -#pragma mark - -#pragma mark Lifecycle methods - -- (id)initWithWindow:(UIWindow *)window { - return [self initWithView:window]; -} - -- (id)initWithView:(UIView *)view { - // Let's check if the view is nil (this is a common error when using the windw initializer above) - if (!view) { - [NSException raise:@"MBProgressHUDViewIsNillException" - format:@"The view used in the MBProgressHUD initializer is nil."]; - } - id me = [self initWithFrame:view.bounds]; - // We need to take care of rotation ourselfs if we're adding the HUD to a window - if ([view isKindOfClass:[UIWindow class]]) { - [self setTransformForCurrentOrientation:NO]; - } - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) - name:UIDeviceOrientationDidChangeNotification object:nil]; - - return me; -} - -- (void)removeFromSuperview { - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIDeviceOrientationDidChangeNotification - object:nil]; - - [super removeFromSuperview]; -} - +#pragma mark - Lifecycle - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; @@ -313,8 +155,8 @@ - (id)initWithFrame:(CGRect)frame { self.labelText = nil; self.detailsLabelText = nil; self.opacity = 0.8f; - self.labelFont = [UIFont boldSystemFontOfSize:LABELFONTSIZE]; - self.detailsLabelFont = [UIFont boldSystemFontOfSize:LABELDETAILSFONTSIZE]; + self.labelFont = [UIFont boldSystemFontOfSize:kLabelFontSize]; + self.detailsLabelFont = [UIFont boldSystemFontOfSize:kDetailsLabelFontSize]; self.xOffset = 0.0f; self.yOffset = 0.0f; self.dimBackground = NO; @@ -324,31 +166,43 @@ - (id)initWithFrame:(CGRect)frame { self.removeFromSuperViewOnHide = NO; self.minSize = CGSizeZero; self.square = NO; - - self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; - + self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin + | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; + // Transparent background self.opaque = NO; self.backgroundColor = [UIColor clearColor]; - - // Make invisible for now + // Make it invisible for now self.alpha = 0.0f; - // Add label - label = [[UILabel alloc] initWithFrame:self.bounds]; - - // Add details label - detailsLabel = [[UILabel alloc] initWithFrame:self.bounds]; - taskInProgress = NO; rotationTransform = CGAffineTransformIdentity; + + [self setupLabels]; + [self updateIndicators]; + [self registerForKVO]; + [self registerForNotifications]; } return self; } +- (id)initWithView:(UIView *)view { + NSAssert(view, @"View must not be nil."); + id me = [self initWithFrame:view.bounds]; + // We need to take care of rotation ourselfs if we're adding the HUD to a window + if ([view isKindOfClass:[UIWindow class]]) { + [self setTransformForCurrentOrientation:NO]; + } + return me; +} + +- (id)initWithWindow:(UIWindow *)window { + return [self initWithView:window]; +} + - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - + [self unregisterFromNotifications]; + [self unregisterFromKVO]; #if !__has_feature(objc_arc) [indicator release]; [label release]; @@ -363,136 +217,14 @@ - (void)dealloc { #endif } -#pragma mark - -#pragma mark Layout - -- (void)layoutSubviews { - CGRect frame = self.bounds; - - // Compute HUD dimensions based on indicator size (add margin to HUD border) - CGRect indFrame = indicator.bounds; - self.width = indFrame.size.width + 2 * margin; - self.height = indFrame.size.height + 2 * margin; - - // Position the indicator - indFrame.origin.x = floorf((frame.size.width - indFrame.size.width) / 2) + self.xOffset; - indFrame.origin.y = floorf((frame.size.height - indFrame.size.height) / 2) + self.yOffset; - indicator.frame = indFrame; - - // Add label if label text was set - if (nil != self.labelText) { - // Get size of label text - CGSize dims = [self.labelText sizeWithFont:self.labelFont]; - - // Compute label dimensions based on font metrics if size is larger than max then clip the label width - float lHeight = dims.height; - float lWidth; - if (dims.width <= (frame.size.width - 4 * margin)) { - lWidth = dims.width; - } - else { - lWidth = frame.size.width - 4 * margin; - } - - // Set label properties - label.font = self.labelFont; - label.adjustsFontSizeToFitWidth = NO; - label.textAlignment = UITextAlignmentCenter; - label.opaque = NO; - label.backgroundColor = [UIColor clearColor]; - label.textColor = [UIColor whiteColor]; - label.text = self.labelText; - - // Update HUD size - if (self.width < (lWidth + 2 * margin)) { - self.width = lWidth + 2 * margin; - } - self.height = self.height + lHeight + PADDING; - - // Move indicator to make room for the label - indFrame.origin.y -= (floorf(lHeight / 2 + PADDING / 2)); - indicator.frame = indFrame; - - // Set the label position and dimensions - CGRect lFrame = CGRectMake(floorf((frame.size.width - lWidth) / 2) + xOffset, - floorf(indFrame.origin.y + indFrame.size.height + PADDING), - lWidth, lHeight); - label.frame = lFrame; - - [self addSubview:label]; - - // Add details label delatils text was set - if (nil != self.detailsLabelText) { - - // Set label properties - detailsLabel.font = self.detailsLabelFont; - detailsLabel.adjustsFontSizeToFitWidth = NO; - detailsLabel.textAlignment = UITextAlignmentCenter; - detailsLabel.opaque = NO; - detailsLabel.backgroundColor = [UIColor clearColor]; - detailsLabel.textColor = [UIColor whiteColor]; - detailsLabel.text = self.detailsLabelText; - detailsLabel.numberOfLines = 0; - - CGFloat maxHeight = frame.size.height - self.height - 2*margin; - CGSize labelSize = [detailsLabel.text sizeWithFont:detailsLabel.font constrainedToSize:CGSizeMake(frame.size.width - 4*margin, maxHeight) lineBreakMode:detailsLabel.lineBreakMode]; - lHeight = labelSize.height; - lWidth = labelSize.width; - - // Update HUD size - if (self.width < lWidth) { - self.width = lWidth + 2 * margin; - } - self.height = self.height + lHeight + PADDING; - - // Move indicator to make room for the new label - indFrame.origin.y -= (floorf(lHeight / 2 + PADDING / 2)); - indicator.frame = indFrame; - - // Move first label to make room for the new label - lFrame.origin.y -= (floorf(lHeight / 2 + PADDING / 2)); - label.frame = lFrame; - - // Set label position and dimensions - CGRect lFrameD = CGRectMake(floorf((frame.size.width - lWidth) / 2) + xOffset, - lFrame.origin.y + lFrame.size.height + PADDING, lWidth, lHeight); - detailsLabel.frame = lFrameD; - - [self addSubview:detailsLabel]; - } - } - - if (square) { - CGFloat max = MAX(self.width, self.height); - if (max <= frame.size.width - 2*margin) { - self.width = max; - } - if (max <= frame.size.height - 2*margin) { - self.height = max; - } - } - - if (self.width < minSize.width) { - self.width = minSize.width; - } - if (self.height < minSize.height) { - self.height = minSize.height; - } -} - -#pragma mark - -#pragma mark Showing and execution +#pragma mark - Show & hide - (void)show:(BOOL)animated { useAnimation = animated; - // If the grace time is set postpone the HUD display if (self.graceTime > 0.0) { - self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime - target:self - selector:@selector(handleGraceTimer:) - userInfo:nil - repeats:NO]; + self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self + selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; } // ... otherwise show the HUD imediately else { @@ -503,21 +235,16 @@ - (void)show:(BOOL)animated { - (void)hide:(BOOL)animated { useAnimation = animated; - // If the minShow time is set, calculate how long the hud was shown, // and pospone the hiding operation if necessary if (self.minShowTime > 0.0 && showStarted) { NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted]; if (interv < self.minShowTime) { - self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) - target:self - selector:@selector(handleMinShowTimer:) - userInfo:nil - repeats:NO]; + self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self + selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; return; } } - // ... otherwise hide the HUD immediately [self hideUsingAnimation:useAnimation]; } @@ -530,6 +257,8 @@ - (void)hideDelayed:(NSNumber *)animated { [self hide:[animated boolValue]]; } +#pragma mark - Timer callbacks + - (void)handleGraceTimer:(NSTimer *)theTimer { // Show the HUD only if the task is still running if (taskInProgress) { @@ -542,88 +271,13 @@ - (void)handleMinShowTimer:(NSTimer *)theTimer { [self hideUsingAnimation:useAnimation]; } -- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { - - methodForExecution = method; -#if __has_feature(objc_arc) - targetForExecution = target; - objectForExecution = object; -#else - targetForExecution = [target retain]; - objectForExecution = [object retain]; -#endif - - // Launch execution in new thread - taskInProgress = YES; - [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; - - // Show HUD view - [self show:animated]; -} - -- (void)launchExecution { -#if !__has_feature(objc_arc) - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; -#endif -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - // Start executing the requested task - [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; -#pragma clang diagnostic pop - // Task completed, update view in main thread (note: view operations should - // be done only in the main thread) - [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; - -#if !__has_feature(objc_arc) - [pool release]; -#endif -} - -- (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context { - [self done]; -} - -- (void)done { - isFinished = YES; - - // If delegate was set make the callback - self.alpha = 0.0f; - - if(delegate != nil) { - if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { - [delegate performSelector:@selector(hudWasHidden:) withObject:self]; - } else if ([delegate respondsToSelector:@selector(hudWasHidden)]) { - [delegate performSelector:@selector(hudWasHidden)]; - } - } - - if (removeFromSuperViewOnHide) { - [self removeFromSuperview]; - } -} - -- (void)cleanUp { - taskInProgress = NO; - - self.indicator = nil; - -#if !__has_feature(objc_arc) - [targetForExecution release]; - [objectForExecution release]; -#endif - - [self hide:useAnimation]; -} - -#pragma mark - -#pragma mark Fade in and Fade out +#pragma mark - Internal show & hide operations - (void)showUsingAnimation:(BOOL)animated { self.alpha = 0.0f; if (animated && animationType == MBProgressHUDAnimationZoom) { self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); } - self.showStarted = [NSDate date]; // Fade in if (animated) { @@ -646,7 +300,7 @@ - (void)hideUsingAnimation:(BOOL)animated { [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.30]; [UIView setAnimationDelegate:self]; - [UIView setAnimationDidStopSelector:@selector(animationFinished: finished: context:)]; + [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)]; // 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden // in the done method if (animationType == MBProgressHUDAnimationZoom) { @@ -662,6 +316,201 @@ - (void)hideUsingAnimation:(BOOL)animated { self.showStarted = nil; } +- (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context { + [self done]; +} + +- (void)done { + isFinished = YES; + self.alpha = 0.0f; + if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { + [delegate performSelector:@selector(hudWasHidden:) withObject:self]; + } + if (removeFromSuperViewOnHide) { + [self removeFromSuperview]; + } +} + +#pragma mark - Threading + +- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { + methodForExecution = method; + targetForExecution = MB_RETAIN(target); + objectForExecution = MB_RETAIN(object); + // Launch execution in new thread + self.taskInProgress = YES; + [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; + // Show HUD view + [self show:animated]; +} + +- (void)launchExecution { + @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + // Start executing the requested task + [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; +#pragma clang diagnostic pop + // Task completed, update view in main thread (note: view operations should + // be done only in the main thread) + [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; + } +} + +- (void)cleanUp { + taskInProgress = NO; + self.indicator = nil; +#if !__has_feature(objc_arc) + [targetForExecution release]; + [objectForExecution release]; +#endif + [self hide:useAnimation]; +} + +#pragma mark - UI + +- (void)setupLabels { + label = [[UILabel alloc] initWithFrame:self.bounds]; + label.adjustsFontSizeToFitWidth = NO; + label.textAlignment = UITextAlignmentCenter; + label.opaque = NO; + label.backgroundColor = [UIColor clearColor]; + label.textColor = [UIColor whiteColor]; + label.font = self.labelFont; + label.text = self.labelText; + [self addSubview:label]; + + detailsLabel = [[UILabel alloc] initWithFrame:self.bounds]; + detailsLabel.font = self.detailsLabelFont; + detailsLabel.adjustsFontSizeToFitWidth = NO; + detailsLabel.textAlignment = UITextAlignmentCenter; + detailsLabel.opaque = NO; + detailsLabel.backgroundColor = [UIColor clearColor]; + detailsLabel.textColor = [UIColor whiteColor]; + detailsLabel.numberOfLines = 0; + detailsLabel.font = self.detailsLabelFont; + detailsLabel.text = self.detailsLabelText; + [self addSubview:detailsLabel]; +} + +- (void)updateIndicators { + + BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; + BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; + + if (mode == MBProgressHUDModeIndeterminate && !isActivityIndicator) { + // Update to indeterminate indicator + [indicator removeFromSuperview]; + self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]); + [(UIActivityIndicatorView *)indicator startAnimating]; + [self addSubview:indicator]; + } + else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { + if (!isRoundIndicator) { + // Update to determinante indicator + [indicator removeFromSuperview]; + self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]); + [self addSubview:indicator]; + } + if (mode == MBProgressHUDModeAnnularDeterminate) { + [(MBRoundProgressView *)indicator setAnnular:YES]; + } + } + else if (mode == MBProgressHUDModeCustomView && customView != indicator) { + // Update custom view indicator + [indicator removeFromSuperview]; + self.indicator = customView; + [self addSubview:indicator]; + } else if (mode == MBProgressHUDModeText) { + [indicator removeFromSuperview]; + self.indicator = nil; + } +} + +#pragma mark - Layout + +- (void)layoutSubviews { + + CGRect bounds = self.bounds; + + // Determine the total widt and height needed + CGFloat maxWidth = bounds.size.width - 4 * margin; + CGSize totalSize = CGSizeZero; + + CGRect indicatorF = indicator.bounds; + indicatorF.size.width = MIN(indicatorF.size.width, maxWidth); + totalSize.width = MAX(totalSize.width, indicatorF.size.width); + totalSize.height += indicatorF.size.height; + + CGSize labelSize = [label.text sizeWithFont:label.font]; + labelSize.width = MIN(labelSize.width, maxWidth); + totalSize.width = MAX(totalSize.width, labelSize.width); + totalSize.height += labelSize.height; + if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { + totalSize.height += kPadding; + } + + CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; + CGSize maxSize = CGSizeMake(maxWidth, remainingHeight); + CGSize detailsLabelSize = [detailsLabel.text sizeWithFont:detailsLabel.font + constrainedToSize:maxSize lineBreakMode:detailsLabel.lineBreakMode]; + totalSize.width = MAX(totalSize.width, detailsLabelSize.width); + totalSize.height += detailsLabelSize.height; + if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { + totalSize.height += kPadding; + } + + totalSize.width += 2 * margin; + totalSize.height += 2 * margin; + + // Position elements + CGFloat yPos = roundf(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset; + CGFloat xPos = xOffset; + indicatorF.origin.y = yPos; + indicatorF.origin.x = roundf((bounds.size.width - indicatorF.size.width) / 2) + xPos; + indicator.frame = indicatorF; + yPos += indicatorF.size.height; + + if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { + yPos += kPadding; + } + CGRect labelF; + labelF.origin.y = yPos; + labelF.origin.x = roundf((bounds.size.width - labelSize.width) / 2) + xPos; + labelF.size = labelSize; + label.frame = labelF; + yPos += labelF.size.height; + + if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { + yPos += kPadding; + } + CGRect detailsLabelF; + detailsLabelF.origin.y = yPos; + detailsLabelF.origin.x = roundf((bounds.size.width - detailsLabelSize.width) / 2) + xPos; + detailsLabelF.size = detailsLabelSize; + detailsLabel.frame = detailsLabelF; + + // Enforce minsize and quare rules + if (square) { + CGFloat max = MAX(totalSize.width, totalSize.height); + if (max <= bounds.size.width - 2 * margin) { + totalSize.width = max; + } + if (max <= bounds.size.height - 2 * margin) { + totalSize.height = max; + } + } + if (totalSize.width < minSize.width) { + totalSize.width = minSize.width; + } + if (totalSize.height < minSize.height) { + totalSize.height = minSize.height; + } + + self.size = totalSize; +} + #pragma mark BG Drawing - (void)drawRect:(CGRect)rect { @@ -676,7 +525,6 @@ - (void)drawRect:(CGRect)rect { CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum); CGColorSpaceRelease(colorSpace); - //Gradient center CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); //Gradient radius @@ -691,11 +539,9 @@ - (void)drawRect:(CGRect)rect { // Center HUD CGRect allRect = self.bounds; // Draw rounded HUD bacgroud rect - CGRect boxRect = CGRectMake(roundf((allRect.size.width - self.width) / 2) + self.xOffset, - roundf((allRect.size.height - self.height) / 2) + self.yOffset, self.width, self.height); - // Corner radius + CGRect boxRect = CGRectMake(roundf((allRect.size.width - size.width) / 2) + self.xOffset, + roundf((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height); float radius = 10.0f; - CGContextBeginPath(context); CGContextSetGrayFillColor(context, 0.0f, self.opacity); CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect)); @@ -707,17 +553,71 @@ - (void)drawRect:(CGRect)rect { CGContextFillPath(context); } -#pragma mark - -#pragma mark Manual oritentation change +#pragma mark - KVO + +- (void)registerForKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; + } +} + +- (void)unregisterFromKVO { + for (NSString *keyPath in [self observableKeypaths]) { + [self removeObserver:self forKeyPath:keyPath]; + } +} -#define RADIANS(degrees) ((degrees * (float)M_PI) / 180.0f) +- (NSArray *)observableKeypaths { + return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", + @"detailsLabelText", @"detailsLabelFont", @"progress", nil]; +} -- (void)deviceOrientationDidChange:(NSNotification *)notification { - if (!self.superview) { +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; + } else { + [self updateUIForKeypath:keyPath]; + } +} + +- (void)updateUIForKeypath:(NSString *)keyPath { + if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"]) { + [self updateIndicators]; + } else if ([keyPath isEqualToString:@"labelText"]) { + label.text = self.labelText; + } else if ([keyPath isEqualToString:@"labelFont"]) { + label.font = self.labelFont; + } else if ([keyPath isEqualToString:@"detailsLabelText"]) { + detailsLabel.text = self.detailsLabelText; + } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { + detailsLabel.font = self.detailsLabelFont; + } else if ([keyPath isEqualToString:@"progress"]) { + if ([indicator respondsToSelector:@selector(setProgress:)]) { + [(id)indicator setProgress:progress]; + } return; } - - if ([self.superview isKindOfClass:[UIWindow class]]) { + [self setNeedsLayout]; + [self setNeedsDisplay]; +} + +#pragma mark - Notifications + +- (void)registerForNotifications { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(deviceOrientationDidChange:) + name:UIDeviceOrientationDidChangeNotification object:nil]; +} + +- (void)unregisterFromNotifications { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)deviceOrientationDidChange:(NSNotification *)notification { + UIView *superview = self.superview; + if (!superview) { + return; + } else if ([superview isKindOfClass:[UIWindow class]]) { [self setTransformForCurrentOrientation:YES]; } else { self.bounds = self.superview.bounds; @@ -725,27 +625,25 @@ - (void)deviceOrientationDidChange:(NSNotification *)notification { } } -- (void)setTransformForCurrentOrientation:(BOOL)animated { - UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; - NSInteger degrees = 0; - +- (void)setTransformForCurrentOrientation:(BOOL)animated { // Stay in sync with the superview if (self.superview) { self.bounds = self.superview.bounds; [self setNeedsDisplay]; } + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + float radians = 0; if (UIInterfaceOrientationIsLandscape(orientation)) { - if (orientation == UIInterfaceOrientationLandscapeLeft) { degrees = -90; } - else { degrees = 90; } + if (orientation == UIInterfaceOrientationLandscapeLeft) { radians = -M_PI_2; } + else { radians = M_PI_2; } // Window coordinates differ! self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); } else { - if (orientation == UIInterfaceOrientationPortraitUpsideDown) { degrees = 180; } - else { degrees = 0; } + if (orientation == UIInterfaceOrientationPortraitUpsideDown) { radians = M_PI; } + else { radians = 0; } } - - rotationTransform = CGAffineTransformMakeRotation(RADIANS(degrees)); + rotationTransform = CGAffineTransformMakeRotation(radians); if (animated) { [UIView beginAnimations:nil context:nil]; @@ -758,14 +656,13 @@ - (void)setTransformForCurrentOrientation:(BOOL)animated { @end -///////////////////////////////////////////////////////////////////////////////////////////// -@implementation MBRoundProgressView - -@synthesize isAnnular = _isAnnular; +@implementation MBRoundProgressView { + float _progress; + BOOL _annular; +} -#pragma mark - -#pragma mark Accessors +#pragma mark - Accessors - (float)progress { return _progress; @@ -776,11 +673,19 @@ - (void)setProgress:(float)progress { [self setNeedsDisplay]; } -#pragma mark - -#pragma mark Lifecycle +- (BOOL)isAnnular { + return _annular; +} + +- (void)setAnnular:(BOOL)annular { + _annular = annular; + [self setNeedsDisplay]; +} + +#pragma mark - Lifecycle - (id)init { - return [self initWithFrame:CGRectMake(0.0f, 0.0f, 37.0f, 37.0f)]; + return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; } - (id)initWithFrame:(CGRect)frame { @@ -788,24 +693,22 @@ - (id)initWithFrame:(CGRect)frame { if (self) { self.backgroundColor = [UIColor clearColor]; self.opaque = NO; - self.isAnnular = NO; + _progress = 0.f; + _annular = NO; } return self; } -#pragma mark - -#pragma mark Drawing +#pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGRect allRect = self.bounds; CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f); - CGContextRef context = UIGraphicsGetCurrentContext(); - if (_isAnnular) { + if (_annular) { // Draw background - CGFloat lineWidth = 5.f; UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; processBackgroundPath.lineWidth = lineWidth; @@ -817,7 +720,6 @@ - (void)drawRect:(CGRect)rect { [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [[UIColor colorWithRed:1 green:1 blue:1 alpha:0.1] set]; [processBackgroundPath stroke]; - // Draw progress UIBezierPath *processPath = [UIBezierPath bezierPath]; processPath.lineCapStyle = kCGLineCapRound; @@ -833,7 +735,6 @@ - (void)drawRect:(CGRect)rect { CGContextSetLineWidth(context, 2.0f); CGContextFillEllipseInRect(context, circleRect); CGContextStrokeEllipseInRect(context, circleRect); - // Draw progress CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2); CGFloat radius = (allRect.size.width - 4) / 2; @@ -848,5 +749,3 @@ - (void)drawRect:(CGRect)rect { } @end - -///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/README.mdown b/README.mdown index e4336f6d5..fb11cd78e 100644 --- a/README.mdown +++ b/README.mdown @@ -1,15 +1,15 @@ MBProgressHUD ============= -MBProgressHUD is an iPhone drop-in class that displays a translucent HUD with a progress indicator and some optional labels while work is being done in a background thread. The HUD is meant as a replacement for the undocumented, private UIKit UIProgressHUD with some additional features. - -MBProgressHUD is iOS4 and iPad compatible and released under the MIT license (see MBProgressHUD.h). +MBProgressHUD is an iOS drop-in class that displays a translucent HUD with a progress indicator and some optional labels while work is being done in a background thread. The HUD is meant as a replacement for the undocumented, private UIKit UIProgressHUD with some additional features. [![](http://dl.dropbox.com/u/378729/MBProgressHUD/1-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/1.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/2-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/2.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/3-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/3.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/4-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/4.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/5-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/5.png) +[![](http://dl.dropbox.com/u/378729/MBProgressHUD/6-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/6.png) +[![](http://dl.dropbox.com/u/378729/MBProgressHUD/7-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/7.png) Adding MBProgressHUD to your project ==================================== @@ -52,6 +52,18 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI Change-log ========== +**Version 0.5** @ 22.03.12 + +- Major source code modernization and cleanup (KVO, layout code, instance vars, etc.). +- New annular determinate mode. +- New text only mode. +- Added a static library project and Xcode 4 workspace. +- Added methods to find and return HUD(s) on a view. +- Various bug fixes. +- Various demo project enhancements (hi-res rescues, new samples). + +IMPORTANT: Requires LLVM 3+. + **Version 0.41** @ 03.01.12 - Support for ARC.