25

In my Project, each of the user interaction events make a network call (Which is TCP, not HTTP). I need Activity Indicator to be global to show from a random UIViewController and hide from NetworkActivityManager Class (a custom class to handle network activities, Which is not a subclass of UIViewController or UIView).

After searching the web I found out that MBProgressHUD is used for the same purpose, but I wasn't able to find out an example on how would I use it globally. (By saying global I mean a singleton object of MBProgressHUD and class methods to SHOW and HIDE it.)

Following is what I have tried yet, but, failed: In AppDelegate.h:

@property (nonatomic, retain) MBProgressHUD *hud;

In AppDelegate.m:

@synthesize hud;

In some random UIViewController object:

appDelegate.hud = [MBProgressHUD showHUDAddedTo:appDelegate.navigationController.topViewController.view animated:YES];
appDelegate.hud.labelText = @"This will take some time.";

And while hiding it, from NetworkActivityManager Class:

[MBProgressHUD hideHUDForView:appDelegate.navigationController.topViewController.view animated:YES];

This makes the project to crash after some time (due to memory issues.) I am using ARC in my project and also, I am using the ARC version of MBProgressHUD.

Am I missing something?

Important Question:

Can I make MBProgressHUD work like UIAlertView? (Saying that I mean implementation of MBProgressHUD independent of UIView -- sa it uses showHUDAddedTo: to present itself) ???

Please Note: In the above code of hiding MBProgressHUD, View may be changed from what it was when showing MBProgressHUD.

Any Help greatly appreciated.

Community
  • 1
  • 1
viral
  • 4,168
  • 5
  • 43
  • 68
  • Note that these days (swift, 2016 etc) this is drastically out of date - simply use Apple's container views [here is a simple tutorial for beginners](http://stackoverflow.com/a/23403979/294884) for anything at all like this... – Fattie Oct 22 '16 at 15:34

9 Answers9

67

You could add this to a class of your liking:

+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
    UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
    hud.labelText = title;
    return hud;
}

+ (void)dismissGlobalHUD {
    UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
    [MBProgressHUD hideHUDForView:window animated:YES];
}

This can be than called on any class. You don't need to keep a strong reference to the HUD when using those class convenience methods.

Depending on your specific situation you'll probably also want to handle cases where a new hud is requested before the other one is hidden. You could eater hide the previous hud when a new comes in or come up with some sort of queueing, etc.

Hiding the previous HUD instance before showing a new one is pretty straightforward.

+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
    UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
    [MBProgressHUD hideAllHUDsForView:window animated:YES];
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
    hud.labelText = title;
    return hud;
}
Matej Bukovinski
  • 6,152
  • 1
  • 36
  • 36
  • what would be `view` in `[MBProgressHUD showHUDAddedTo:view animated:YES];` – viral Aug 22 '12 at 17:53
  • Which is a good thing. In theory you could do something like [this](https://gist.github.com/51201edcc84853e88c7c) to have a constant reference to it, but that will cause all kinds of trouble if you call show: before before hide: is completely done animating out, so I would strongly recommend that you don't do that. – Matej Bukovinski Aug 24 '12 at 09:46
  • 3
    If using this way, the progress HUD does not rotate with device orientation. Anyone got solution? – Khawar Sep 06 '13 at 06:16
  • 8
    This worked a lot better for me: `UIWindow *window = [[UIApplication sharedApplication] delegate].window`, and it solves the rotation problem – David Lawson Nov 04 '13 at 23:03
  • Don't mention AppDelegate...it might scare people away before they even read your answer. – septerr Jan 02 '14 at 02:37
  • It will not work when your application is in Landscape mode only. It shows the hud in vertical alignment. I am facing the same issue. – ManiaChamp Nov 12 '14 at 06:44
  • Its working , but i am facing a problem when i poptoviewcontroller the indicator showing on the left bar button. – Ravi Ojha Dec 16 '15 at 09:20
  • I follow this approach, but it results in flickering when trying to add multiple(more than 10) HUD at a time. To avoid flickering and to add only one HUD at a time, [see this answer](https://stackoverflow.com/a/44802673/988169). – pkc456 Jun 28 '17 at 12:38
8

NOTE...

as with many iOS issues, this is now drastically, totally out of date.

These days you certainly just use a trivial

Container view

for any issue like this.

Full container view tutorial for beginners .. tutorial!

MBProgressHUD was a miraculous solution back in the day, because there was a "drastic hole" in Apple's pipeline.

But (as with many wonderful things from the past), this is only history now. Don't do anything like this today.


Just FWIW, 2014, here's a very simple setup we use. Per David Lawson...

UIWindow *window = [[UIApplication sharedApplication] delegate].window

as Matej says, just use AppDelegate...

#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])

AppDelegate.h

// our convenient huddie system (messages with a hud, spinner)
@property (nonatomic, strong) MBProgressHUD *hud;
-(void)huddie;

AppDelegate.m

-(void)huddie
{
// centralised location for MBProgressHUD
[self.hud hide:YES];

UIWindow *windowForHud = [[UIApplication sharedApplication] delegate].window;
self.hud = [MBProgressHUD showHUDAddedTo:windowForHud animated:YES];

self.hud.dimBackground = YES;
self.hud.minShowTime = 0.1;
self.hud.labelText = @"";
self.hud.detailsLabelText = @"";
}

Set the titles in your code where you are using it - because you very often change them during a run. ("Step 1" ... "Step 2" etc)

-(void)loadBlahFromCloud
{
[APP huddie];
APP.hud.labelText = @"Connecting to Parse...";
APP.hud.detailsLabelText = @"step 1/2";

[blah refreshFromCloudThen:
    ^{
    [... example];
    }];
}

-(void)example
{
APP.hud.labelText = @"Connecting to the bank...";
APP.hud.detailsLabelText = @"step 2/2";

[blah sendDetailsThen:
    ^{
    [APP.hud hide:YES];
    [...  showNewDisplay];
    }];
}

Change huddle to take the texts as an argument if you wish

You always want self.hud.minShowTime = 0.1; to avoid flicker

Almost always self.hud.dimBackground = YES; which also blocks UI

Conceptually of course you usually have to "slightly wait" to begin work / end work when you bring up such a process, as with any similar programming with the UI.

So in practice code will usually look like this...

-(void)loadActionSheets
{
[APP huddie];
APP.hud.labelText = @"Loading json from net...";

dispatch_after_secs_on_main(0.1 ,
    ^{
    [STUBS refreshNowFromCloudThen:
        ^{
        [APP.hud hide:YES];
        dispatch_after_secs_on_main(0.1 , ^{ [self buildActionsheet]; });
        }];
        }
    );
}

Handy macro ..

#define dispatch_after_secs_on_main( SS, BB )                   \
        dispatch_after(                                         \
            dispatch_time(DISPATCH_TIME_NOW, SS*NSEC_PER_SEC),  \
            dispatch_get_main_queue(),                          \
            BB                                                  \
            )

This is all history now :) https://stackoverflow.com/a/23403979/294884

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
3

This answer is what I've been using for 5-6 Apps now because it works perfectly inside blocks too. However I found a problem with it. I can make it shown, but can't make it disappear if a UIAlertView is also present. If you look at the implementation you can see why. Simply change it to this:

static UIWindow *window;

+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {

    window = [[[UIApplication sharedApplication] windows] lastObject];
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
    hud.labelText = title;

    return hud;
}

+ (void)dismissGlobalHUD {

    [MBProgressHUD hideHUDForView:window animated:YES];
}

This will make sure you're removing the HUD from the same windows as it was shown on.

Community
  • 1
  • 1
Zoltan Varadi
  • 2,468
  • 2
  • 34
  • 51
2

I found @Matej Bukovinski 's answer very helpful, since I just started using Swift and my purpose using his methods was to set a global font for the MBProgressHUD, I have converted the code to swift and am willing to share the code below:

class func showGlobalProgressHUDWithTitle(title: String) -> MBProgressHUD{
    let window:UIWindow = UIApplication.sharedApplication().windows.last as! UIWindow
    let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
    hud.labelText = title
    hud.labelFont = UIFont(name: FONT_NAME, size: 15.0)
    return hud
}

class func dismissGlobalHUD() -> Void{
    let window:UIWindow = UIApplication.sharedApplication().windows.last as! UIWindow
    MBProgressHUD.hideAllHUDsForView(window, animated: true)
}

The above code is put into a global file where I keep all my global helpers and constants.

Michael Shang
  • 686
  • 8
  • 9
1

I've used it as below..Hope it helps you..

in appDelegate.m

-(void)showIndicator:(NSString *)withTitleString currentView:(UIView *)currentView
{ 
if (!isIndicatorStarted) {

    // The hud will dispable all input on the view
    self.progressHUD = [[[MBProgressHUD alloc] initWithView:currentView] autorelease]; 
    // Add HUD to screen 
    [currentView addSubview:self.progressHUD]; 
    self.progressHUD.labelText = withTitleString;
    [window setUserInteractionEnabled:FALSE];
    [self.progressHUD show:YES];

    isIndicatorStarted = TRUE;
}   
 } 

-(void)hideIndicator 
{ 

    [self.progressHUD show:NO]; 
    [self.progressHUD removeFromSuperview]; 
    self.progressHUD = nil;
    [window setUserInteractionEnabled:TRUE];
    isIndicatorStarted = FALSE;
}

From Random Views:-

[appDel showIndicator:@"Loading.." currentView:presentView.view];

Maulik
  • 19,348
  • 14
  • 82
  • 137
  • How would I call it to show from the class which is not UIView or UIViewController subclass. This is the reason why I want to make it independent of UIViews. Like UIAlertView, any idea on this ??? – viral Aug 20 '12 at 06:44
  • Any class which represent MODEL of the project, not VIEWS or CONTROLLERS. – viral Aug 20 '12 at 06:59
1

Note: Considering the views this Question is getting I decided to post the the way I did choose as a solution. This is NOT an answer to my question. (Hence, the accepted answer remains accepted)

At that time I ended up using SVProgressHUD as it was very simple to integrate and use.

All you need to do is just drag the SVProgressHUD/SVProgressHUD folder into your project. (You may choose to go for cocoapods OR carthage, as well)


In Objective-C:

[SVProgressHUD show]; // Show
[SVProgressHUD dismiss]; // Dismiss

In Swift:

SVProgressHUD.show() // Show
SVProgressHUD.dismiss() // Dismiss

Additionally, Show and hide HUD needs to be executed on main thread. (Specifically you would need this to hide the HUD in some closure in background)

e.g.:

dispatch_async(dispatch_get_main_queue(), ^{
    [SVProgressHUD dismiss]; // OR SHOW, whatever the need is.
});

There are additional methods for displaying custom messages with HUD, showing success/failure for short duration and auto dismiss.

MBProgressHUD still remains a good choice for developers. It's just that I found SVProgressHUD to suit my needs.

viral
  • 4,168
  • 5
  • 43
  • 68
  • agree, SVProgressHUD is so easy to use. – Yao Li Nov 29 '17 at 22:49
  • It is not fully customisable like MBProgressHud. I tried to clear background blur part, I want it transparent but not possible as far as I know. Still MBProgressHud is better for now.. – Dharmik May 23 '20 at 06:16
0

I was using the code from @Michael Shang and having all kinds of inconsistent behavior with showing HUDs. Turns out using the last window is unreliable as the iOS keyboard may just hide it. So in the majority of cases you should get the window using the AppDelegate as mentioned by @David Lawson.

Here's how in Swift:

let window = UIApplication.sharedApplication().delegate!.window!!
let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)

However, with the above your HUD will show up behind the iOS keyboard (if they overlap). If you need your HUD to overlay the keyboard use the last window method.

In my case, what was happening is I would show the HUD then call resignFirstResponder() immediately hiding the window the HUD was added to. So this is something to be aware of, the only window guaranteed to stick around is the first one.

I ended up creating a method that could optionally add the HUD above the keyboard if needed:

func createHUD(size: CGSize, overKeyboard: Bool = false) -> MBProgressHUD {
    let window = overKeyboard ? UIApplication.sharedApplication().windows.last!
                              : UIApplication.sharedApplication().delegate!.window!!
    let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
    hud.minSize = size
    hud.bezelView.style = .SolidColor
    hud.bezelView.color = UIColor(white: 0, alpha: 0.8)
    return hud
}
Code Baller
  • 71
  • 2
  • 4
0

To show the one MBProgressHUD at one time, you can check weather HUD is already added in same view or not. If not, then add the HUD otherwise do not add new HUD.

-(void)showLoader{    
    dispatch_async(dispatch_get_main_queue(), ^{
        BOOL isHudAlreadyAdded = false;

        UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
        NSEnumerator *subviewsEnum = [window.subviews reverseObjectEnumerator];
        for (UIView *subview in subviewsEnum) {
            if ([subview isKindOfClass:[MBProgressHUD class]]) {
                isHudAlreadyAdded = true;
            }
        }
        if(isHudAlreadyAdded == false){
            [MBProgressHUD showHUDAddedTo:window animated:YES];
        }
    });
}

-(void)hideLoader{
    dispatch_async(dispatch_get_main_queue(), ^{
        UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
        [MBProgressHUD hideHUDForView:window animated:YES];
    });
}
pkc456
  • 8,350
  • 38
  • 53
  • 109
-1

Add these two methods to show or hide loader in your singleton class

- (void)startLoaderWithText:(NSString *)title View:(UIView *)view{
    progressHud = [MBProgressHUD showHUDAddedTo:view animated:YES];
    progressHud.labelText = title;
    progressHud.activityIndicatorColor = [UIColor grayColor];
    progressHud.color = [UIColor clearColor];
    [progressHud show:YES];
}

- (void)stopLoader{
    [progressHud hide:YES];
}
viral
  • 4,168
  • 5
  • 43
  • 68
shweta sharma
  • 11
  • 1
  • 3