How do I put a UIActivityIndicatorView in a UIAlertController?

Ben Leggiero picture Ben Leggiero · Jul 14, 2015 · Viewed 7.9k times · Source

We're moving away from MBProgressHUD because it's too glitchy in our app, and doesn't have features such as blocking user input or providing a Cancel button.

So, I've attempted to implement Swipesight's How to display activity indicator in center of UIAlertController?, and I ran into improper positioning of the indicator:

A <code>UIAlertController</code> with "Loading" and censored text in the center of the screen, but the activity indicator far to the left and below the alert controller
It is green because our app's tint is green.

As you can see, it's not in the white rectangle part of the controller, but the grey background. This uses something similar to his "@62Shark" solution:

// in implementation:

@property (nonatomic, strong) UIActivityIndicatorView *spinner;
@property (nonatomic, strong) UIAlertController *alertController;


// in init:

_alertController = [UIAlertController alertControllerWithTitle: @"Loading"
                                                         message: nil
                                                  preferredStyle: UIAlertControllerStyleAlert];
_spinner = [UIActivityIndicatorView new];
_spinner.translatesAutoresizingMaskIntoConstraints = false;
_spinner.userInteractionEnabled = false;
_spinner.color = [ThemingAssistant tintColor];
_spinner.frame = _alertController.view.bounds;
_spinner.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_spinner startAnimating];
[_alertController.view addSubview: _spinner];


// ...

- (void) showOn: (UIViewController *)target
          title: (NSString *)title
        message: (NSString *)message
      canCancel: (BOOL)canCancel
{
    self.alertController.title = title;
    self.alertController.message = message;

    if (canCancel)
    {
        [self.alertController addAction:[UIAlertAction actionWithTitle: @"Cancel"
                                                                 style: UIAlertActionStyleCancel
                                                               handler: ^(UIAlertAction *name){
                                                                   [self customDismiss];
                                                               }]];
    }

    NSDictionary *views = @{@"pending"   : self.alertController.view,
                            @"indicator" : self.spinner};
    NSArray *constraints =
    [NSLayoutConstraint constraintsWithVisualFormat: @"V:[indicator]-(-50)-|"
                                            options: 0
                                            metrics: nil
                                              views: views];
    [constraints arrayByAddingObjectsFromArray:
     [NSLayoutConstraint constraintsWithVisualFormat: @"H:|[indicator]|"
                                             options: 0
                                             metrics: nil
                                               views: views]];


    [target presentViewController: self.alertController
                         animated: true
                       completion: nil];
}

Even if this was in the white rectangle, I fear it might run into text (also, when I get it in there, I want it to be in the middle-top, much like MBProgressHUD does), so I'll need a way to reserve some space for it.

So, my question is two-fold: How do I reserve space for a UIActivityIndicatorView in a UIAlertController's white rectangle, and then how do I actually place it in there?

Answer

Alaeddine picture Alaeddine · Jul 14, 2015

Like JonasG mentioned here there is a property named contentViewController and we can use KVC for access

Example :

UIViewController *v = [[UIViewController alloc] init];
v.view.backgroundColor = [UIColor redColor];

[alertController setValue:v forKey:@"contentViewController"];

So here is how your code should looks like (tested and works fine) :

- (IBAction)buttonClicked:(id)sender
{

    self.alertController = [UIAlertController alertControllerWithTitle: @"Loading"
                                                           message: nil
                                                    preferredStyle: UIAlertControllerStyleAlert];


    [self.alertController addAction:[UIAlertAction actionWithTitle: @"Cancel" style: UIAlertActionStyleCancel handler:nil]];


    UIViewController *customVC     = [[UIViewController alloc] init];


    UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [spinner startAnimating];
    [customVC.view addSubview:spinner];


    [customVC.view addConstraint:[NSLayoutConstraint
                           constraintWithItem: spinner
                           attribute:NSLayoutAttributeCenterX
                           relatedBy:NSLayoutRelationEqual
                           toItem:customVC.view
                           attribute:NSLayoutAttributeCenterX
                           multiplier:1.0f
                           constant:0.0f]];



    [customVC.view addConstraint:[NSLayoutConstraint
                           constraintWithItem: spinner
                           attribute:NSLayoutAttributeCenterY
                           relatedBy:NSLayoutRelationEqual
                           toItem:customVC.view
                           attribute:NSLayoutAttributeCenterY
                           multiplier:1.0f
                           constant:0.0f]];


    [self.alertController setValue:customVC forKey:@"contentViewController"];


    [self presentViewController: self.alertController
                         animated: true
                       completion: nil];

}

You can override -preferredContentSize to return a custom size in the view controller that you are setting as contentViewController.

in our case it's customVC

Result:

alert

Want the text to be below the indicator ?

I have created a UIViewController with an xib to act as a custom controller for our contentViewController in the first example we have created the view controller without xib file, Now we can add views using interface builder, I have added an Activity indicator and set the constraints to be centered horizontally and vertically and a label under the activity indicator which is centered horizontally here is my interface builder:

contentViewController

we have less code now:

- (IBAction)buttonClicked:(id)sender
{

    self.alertController = [UIAlertController alertControllerWithTitle: nil
                                                           message: nil
                                                    preferredStyle: UIAlertControllerStyleAlert];

    MyCustomViewController *v     = [[MyCustomViewController alloc] init];

    [self.alertController setValue:v forKey:@"contentViewController"];
    [self presentViewController: self.alertController animated: true  completion: nil];

}

Result :

Custom alert