28

So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.

    HUD = [[MBProgressHUD alloc] initWithView:self.view];
    [self.view addSubview:HUD];

    HUD.delegate = self;
    HUD.labelText = @"Loading";

    [HUD show:YES];

This is the result:

result.

Is there any way to resolve this issue, or should I just abandon MBProgressHUD?

Thanks!

AVerguno
  • 1,277
  • 11
  • 27
Chris Ballinger
  • 796
  • 8
  • 20

10 Answers10

41

My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.

HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];

This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.

An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.

Tyler Collier
  • 11,489
  • 9
  • 73
  • 80
  • Nice solution, unfortunately due to the merging of the navigation controls with the status bar in iOS 7, the status bar is also covered by the overlay. – newenglander Mar 15 '14 at 10:31
  • this worked for my use case. It also had the added benefit of keeping the "Saving.." dialog visible while the underlying table view controller popped off the stack with animation. – Chad Pavliska Jan 17 '15 at 03:19
  • sadly, my app doesn't have a navigation controller, is there really no means of finding the super view with ios? – armnotstrong Dec 04 '17 at 14:50
15

It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.

jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • What do you mean by "add HUD directly to the window"? `[self.view.window addSubview:HUD];` doesn't seem to work. I tried changing to a UIViewController and it displayed the HUD just fine, but for some reason all of my UITableView's elements showed up at the bottom of a rather large table. When I change it back to UITableViewController the content displays properly again. – Chris Ballinger Aug 26 '11 at 04:01
  • "doesn't seem to work" in what way? It should work just fine, I think. And I'm not sure what you're doing wrong with the UIViewController, but make sure your autoresizing mask is set right. – jtbandes Aug 26 '11 at 04:09
  • I ended up just deleting the view and re-making it again in Interface Builder and for some reason it worked this time (as a UIViewController). Thanks man! – Chris Ballinger Aug 26 '11 at 04:11
  • 2
    likely self.view.window was called before viewWillAppear - it is nil until then. – TomSwift Mar 01 '12 at 23:11
  • 1
    Thanks @jtbandes, your solution i-e adding HUD in the self.view.window works great for me but i still have a problem, when my application loads the first view controller i-e rootViewController then it crashes. I dont know why it is happening, can anybody tell me what the problem can be? It seems that TomSwift is right i-e self.view.window is nil but cannot find the solution. Please help – iMemon Apr 11 '13 at 08:39
  • Simple solution: Don't call `self.view.window` directly from `viewWillAppear`. Instead, call it from a `dispatch_async` within `viewWillAppear`. That will delay it just long enough that the window will be non-nil. – Steven Fisher Feb 16 '15 at 18:48
3

My solution was:

self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];

Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)

Kennedy Mulenga
  • 159
  • 1
  • 1
2

Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)

#import "UITableView+MBProgressView.h"

@implementation UITableView (MBProgressView)


- (void)didAddSubview:(UIView *)subview{
    for (UIView *view in self.subviews){
        if([view isKindOfClass:[MBProgressHUD class]]){
            [self bringSubviewToFront:view];
            break;
        }
    }
}

@end
  • Why would you iterate through each subview when the one you need is passed into the method? – Scott McMillin Jun 19 '13 at 17:12
  • Because the way the tableview work and MBProgressHUD you add MBProgressView way before any cell are added to the TableView, if you do remove the for loop you won't end up with MBProgressHUD always in front. – Mathieu Juneau Oct 04 '13 at 12:01
2

A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.

Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.

Community
  • 1
  • 1
Carl Atupem
  • 66
  • 1
  • 3
1

I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:

Add the following line at the beginning of your UITableViewController subclass implementation:

@synthesize tableView;

Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):

if (!tableView &&
    [self.view isKindOfClass:[UITableView class]]) {
    tableView = (UITableView *)self.view;
}

self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];

Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).

CGee
  • 1,650
  • 5
  • 20
  • 31
1

I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.

In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.

i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.

-(void) viewDidAppear:(BOOL)animated{
    if (flag) {
        [self requestSomeData];
    }
    flag = YES;
    [super viewDidAppear:animated];
}
Mina Kolta
  • 1,551
  • 2
  • 12
  • 22
0

I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.

You need to do the following changes:

Header file:

@interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
@end

Implementation file:

@interface YourViewController ()
@property (nonatomic, retain) UITableView     *tableView;
@property (nonatomic, retain) MBProgressHUD   *hud;
@end

@implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
    self = [super init];
    if (self) {
        // create and configure the table view
        _tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
        _tableView.delegate   = self;
        _tableView.dataSource = self;
    }
    return self;
}

- (void)dealloc
{
    self.tableView = nil;
    self.hud = nil;

    [super dealloc];
}

#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
    CGRect frame = [self boundsFittingAvailableScreenSpace];
    self.view = [[[UIView alloc] initWithFrame:frame] autorelease];

    // add UI elements
    self.tableView.frame = self.view.bounds;
    [self.view addSubview:self.tableView];
}

- (void)viewWillDisappear:(BOOL)animated
{
    // optionally
    [self cancelWhateverYouWereWaitingFor];
    [self.hud hide:animated];
}

The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.

Tafkadasoh
  • 4,667
  • 4
  • 27
  • 31
0

In .h

#import "AppDelegate.h"

@interface ViewController : UITableViewController
{
    MBProgressHUD *progressHUD;
    ASAppDelegate *appDelegate;
}

In .m

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = @"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
ArNo
  • 2,278
  • 1
  • 22
  • 19
0

This should work.

  [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];

And to remove you can try

  [MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
Kaey
  • 4,615
  • 1
  • 14
  • 18