How to display activity indicator in center of UIAlertController?

I converted the answer to Objective C, if anyone is interested:

UIAlertController *pending = [UIAlertController alertControllerWithTitle:nil
                                                               message:@"Please wait...\n\n"
                                                        preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView* indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.color = [UIColor blackColor];
indicator.translatesAutoresizingMaskIntoConstraints=NO;
[pending.view addSubview:indicator];
NSDictionary * views = @{@"pending" : pending.view, @"indicator" : indicator};

NSArray * constraintsVertical = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[indicator]-(20)-|" options:0 metrics:nil views:views];
NSArray * constraintsHorizontal = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[indicator]|" options:0 metrics:nil views:views];
NSArray * constraints = [constraintsVertical arrayByAddingObjectsFromArray:constraintsHorizontal];
[pending.view addConstraints:constraints];
[indicator setUserInteractionEnabled:NO];
[indicator startAnimating];
[self presentViewController:pending animated:YES completion:nil];

Cheers


tl;dr

All the other answers are off :) See documentation:

Important

The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.

Problem

The problem is not the UIAlertController. This is a very simple UI, a stackview or two depending if you want the UIActivityIndicatorView left to the title label or under the title. The presentation animation is what we want.

The code below is based on the WWDC session A Look Inside Presentation Controllers.

Swift

Recreate Presentation Controller:

class LOActivityAlertControllerPresentationController: UIPresentationController {
    
    var dimmerView: UIView!
    
    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        self.dimmerView = UIView()
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        dimmerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        dimmerView.backgroundColor = UIColor.init(white: 0, alpha: 0.4)
        
        guard let presentedView = self.presentedView else { return }
        presentedView.layer.cornerRadius = 8.0
        
        let centerXMotionEffect: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
        centerXMotionEffect.minimumRelativeValue = -10.0
        centerXMotionEffect.maximumRelativeValue = 10.0
        
        let centerYMotionEffect: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
        centerYMotionEffect.minimumRelativeValue = -10.0
        centerYMotionEffect.maximumRelativeValue = 10.0
        
        let group: UIMotionEffectGroup = UIMotionEffectGroup()
        group.motionEffects = [centerXMotionEffect, centerYMotionEffect]
        
        presentedView.addMotionEffect(group)
    }
    
    override var frameOfPresentedViewInContainerView: CGRect {
        guard let containerView = self.containerView, let presentedView = self.presentedView else { return .zero }
        
        let size = presentedView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
        var frame = CGRect.zero
        
        frame.origin = CGPoint(x: containerView.frame.midX - (size.width / 2.0), y: containerView.frame.midY - (size.height / 2.0))
        
        frame.size = size
        
        return frame
    }
    
    override func presentationTransitionWillBegin() {
        guard let containerView: UIView = self.containerView, let presentedView: UIView = self.presentedView, let dimmerView = self.dimmerView else { return }
        let presentingViewController: UIViewController = self.presentingViewController
        
        dimmerView.alpha = 0.0
        dimmerView.frame = containerView.bounds
        containerView.insertSubview(dimmerView, at: 0)
        
        presentedView.center = containerView.center
        
        guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return }
        
        transitionCoordinator.animate(
            alongsideTransition: { _ in
                dimmerView.alpha = 1.0
            },
            completion: nil
        )
    }
    
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        
        guard let containerView: UIView = self.containerView, let presentedView: UIView = self.presentedView, let dimmerView = self.dimmerView else { return }
        
        dimmerView.frame = containerView.bounds
        presentedView.frame = self.frameOfPresentedViewInContainerView
    }
    
    override func dismissalTransitionWillBegin() {
        guard let dimmerView = self.dimmerView, let transitionCoordinator = self.presentingViewController.transitionCoordinator else { return }
        
        transitionCoordinator.animate(
            alongsideTransition: { _ in
                dimmerView.alpha = 0.0
            },
            completion: nil
        )
    }

}

Animated Transitioning:

class LOActivityAlertControllerAnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
    
    var presentation: Bool
    
    init(presentation: Bool) {
        self.presentation = presentation
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        guard let fromView = transitionContext.view(forKey: .from), let toView = transitionContext.view(forKey: .to) else { return }
        if self.presentation {
            containerView.addSubview(toView)
            toView.transform = CGAffineTransform(scaleX: 1.6, y: 1.6)
            toView.alpha = 0.0
            UIView.animate(
                withDuration: 0.2,
                animations: {
                    toView.alpha = 1.0
                    toView.transform = .identity
                },
                completion: { finished in
                    transitionContext.completeTransition(true)
                }
            )
        } else {
            UIView.animate(
                withDuration: 0.2,
                animations: {
                    fromView.alpha = 0.0
                },
                completion: { finished in
                    fromView.removeFromSuperview()
                    transitionContext.completeTransition(true)
                }
            )
        }
    }
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.2
    }
    
}

Sample UIViewController subclass, season to taste with XIB:

class LOActivityAlertController: UIViewController, UIViewControllerTransitioningDelegate {
    
    var activityIndicatorView: UIActivityIndicatorView!
    var titleLabel: UILabel!
    var messageLabel: UILabel!
    
    var alertTitle: String
    var alertMessage: String
    
    init(title: String, message: String) {
        self.alertTitle = title
        self.alertMessage = message
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("Not implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.transitioningDelegate = self
        self.modalPresentationStyle = .custom
        self.titleLabel = UILabel()
        self.messageLabel = UILabel()
        self.titleLabel.text = self.alertTitle
        self.messageLabel.text = self.alertMessage
        
        self.activityIndicatorView = UIActivityIndicatorView(style: .medium)
        
        let currentFrame = self.view.frame
        let alertFrame = CGRect(x: 0, y: 0, width: currentFrame.width / 2.0, height: currentFrame.height / 2.0)
        
        let stackView = UIStackView(frame: alertFrame)
        stackView.backgroundColor = .gray
        stackView.axis = .vertical
        stackView.alignment = .center
        stackView.distribution = .fillProportionally
        stackView.addArrangedSubview(self.titleLabel)
        stackView.addArrangedSubview(self.messageLabel)
        stackView.addArrangedSubview(self.activityIndicatorView)
        
        self.activityIndicatorView.startAnimating()
        
        self.view.addSubview(stackView)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        
    }
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let presentationController = LOActivityAlertControllerPresentationController(presentedViewController: presented, presenting: presenting)
        return presentationController
    }
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        let transitioning = LOActivityAlertControllerAnimatedTransitioning(presentation: true)
        return transitioning
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        let transitioning = LOActivityAlertControllerAnimatedTransitioning(presentation: false)
        return transitioning
    }
}

Credits for swift version: @riciloma

Objective-C

Recreate Presentation Controller:

@interface LOActivityAlertControllerPresentationController : UIPresentationController
@end

@interface LOActivityAlertControllerPresentationController ()
@property (nonatomic) UIView *dimmerView;
@end

@implementation LOActivityAlertControllerPresentationController

- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController
{
    self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
    if (self)
    {
        _dimmerView = [[UIView alloc] init];
        _dimmerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        _dimmerView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4];
        
        
        UIView *presentedView = [self presentedView];
        presentedView.layer.cornerRadius = 8.0;
        
        UIInterpolatingMotionEffect *centerXMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
        centerXMotionEffect.minimumRelativeValue = @(-10.0);
        centerXMotionEffect.maximumRelativeValue = @(10.0);
        
        UIInterpolatingMotionEffect *centerYMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
        centerYMotionEffect.minimumRelativeValue = @(-10.0);
        centerYMotionEffect.maximumRelativeValue = @(10.0);
        
        UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
        group.motionEffects = [NSArray arrayWithObjects:centerXMotionEffect, centerYMotionEffect, nil];
        
        [presentedView addMotionEffect:group];
    }
    return self;
    
}

- (CGRect)frameOfPresentedViewInContainerView
{
    UIView *containerView = [self containerView];
    UIView *presentedView = [self presentedView];
    
    CGSize size = [presentedView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    CGRect frame = CGRectZero;
    frame.origin = CGPointMake(CGRectGetMidX([containerView frame]) - (size.width / 2.0),
                               CGRectGetMidY([containerView frame]) - (size.height / 2.0));
    frame.size = size;
    
    return frame;
}

- (void)presentationTransitionWillBegin
{
    UIViewController *presentingViewController = [self presentingViewController];
    UIView *containerView = [self containerView];
    UIView *presentedView = [self presentedView];
    UIView *dimmerView = [self dimmerView];
    
    dimmerView.alpha = 0.0;
    dimmerView.frame = [containerView bounds];
    [containerView insertSubview:dimmerView atIndex:0];
    
    presentedView.center = [containerView center];
    
    [[presentingViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        
        dimmerView.alpha = 1.0;
        
    } completion:NULL];
}

- (void)containerViewWillLayoutSubviews
{
    [super containerViewWillLayoutSubviews];
    
    UIView *containerView = [self containerView];
    UIView *presentedView = [self presentedView];
    UIView *dimmerView = [self dimmerView];
    
    dimmerView.frame = [containerView bounds];
    presentedView.frame = [self frameOfPresentedViewInContainerView];
}

- (void)dismissalTransitionWillBegin
{
    UIViewController *presentingViewController = [self presentingViewController];
    UIView *dimmerView = [self dimmerView];
    
    [[presentingViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        
        dimmerView.alpha = 0.0;
        
    } completion:NULL];
}


@end

Animated Transitioning:

@interface LOActivityAlertControllerAnimatedTransitioning : NSObject <UIViewControllerAnimatedTransitioning>

@property (getter=isPresentation) BOOL presentation;

@end

@implementation LOActivityAlertControllerAnimatedTransitioning

- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext
{
    UIView *containerView = [transitionContext containerView];
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    if (_presentation)
    {
        [containerView addSubview:toView];
        toView.transform = CGAffineTransformMakeScale(1.6, 1.6);
        toView.alpha = 0.0;
        [UIView animateWithDuration:0.2 animations:^{
            
            toView.alpha = 1.0;
            toView.transform = CGAffineTransformIdentity;
            
        } completion:^(BOOL finished) {
            
            [transitionContext completeTransition:YES];
            
        }];
    }
    else
    {
        [UIView animateWithDuration:0.2 animations:^{
            
            fromView.alpha = 0.0;
            
        } completion:^(BOOL finished) {
            
            [fromView removeFromSuperview];
            [transitionContext completeTransition:YES];
            
        }];
    }
}

- (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext
{
    return 0.2;
}

@end

Sample UIViewController subclass, season to taste with XIB:

@interface LOActivityAlertController : UIViewController <UIViewControllerTransitioningDelegate>

@property (nonatomic, strong) IBOutlet UIActivityIndicatorView *activityIndicatorView;
@property (nonatomic, strong) IBOutlet UILabel *titleLabel;

@end

@implementation LOActivityAlertController

@dynamic title;

+ (instancetype)alertControllerWithTitle:(NSString *)title
{
    LOActivityAlertController *alert = [LOActivityAlertController new];
    alert.title = title;
    return alert;
}

- (instancetype)init
{
    self = [super init];
    if (self)
    {
        self.transitioningDelegate = self;
        self.modalPresentationStyle = UIModalPresentationCustom;
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.titleLabel.text = self.title;
}

#pragma mark Properties

- (void)setTitle:(NSString *)title
{
    [super setTitle:title];
    
    self.titleLabel.text = title;
}

#pragma mark UIViewControllerTransitioningDelegate

- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented
                                                      presentingViewController:(UIViewController *)presenting
                                                          sourceViewController:(UIViewController *)source
{
    LOActivityAlertControllerPresentationController *myPresentation = nil;
    myPresentation = [[LOActivityAlertControllerPresentationController alloc]
                      initWithPresentedViewController:presented presentingViewController:presenting];
    
    return myPresentation;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
{
    LOActivityAlertControllerAnimatedTransitioning *transitioning = [LOActivityAlertControllerAnimatedTransitioning new];
    transitioning.presentation = YES;
    return transitioning;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    LOActivityAlertControllerAnimatedTransitioning *transitioning = [LOActivityAlertControllerAnimatedTransitioning new];
    return transitioning;
}

@end

Screen Recording

enter image description here

Bug Reporter

rdar://37433306: Make UIAlertController presentation controller and transitioning delegate public API to enable reuse.


Be sure to set the frame property when you're creating a view.

func displaySignUpPendingAlert() -> UIAlertController {
        //create an alert controller
        let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)

        //create an activity indicator
        let indicator = UIActivityIndicatorView(frame: pending.view.bounds)
        indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        //add the activity indicator as a subview of the alert controller's view
        pending.view.addSubview(indicator)
        indicator.isUserInteractionEnabled = false // required otherwise if there buttons in the UIAlertController you will not be able to press them
        indicator.startAnimating()

        self.presentViewController(pending, animated: true, completion: nil)

        return pending
}

To @62Shark:

let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)

let indicator = UIActivityIndicatorView()
indicator.setTranslatesAutoresizingMaskIntoConstraints(false)
pending.view.addSubview(indicator)

let views = ["pending" : pending.view, "indicator" : indicator]
var constraints = NSLayoutConstraint.constraintsWithVisualFormat("V:[indicator]-(-50)-|", options: nil, metrics: nil, views: views)
constraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[indicator]|", options: nil, metrics: nil, views: views)
pending.view.addConstraints(constraints)

indicator.userInteractionEnabled = false
indicator.startAnimating()

self.presentViewController(pending, animated: true, completion: nil)

Swift 5.0 solution

let alert = UIAlertController(title: "Sender ...", message: nil, preferredStyle: .alert)
let activityIndicator = UIActivityIndicatorView(style: .gray)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isUserInteractionEnabled = false
activityIndicator.startAnimating()

alert.view.addSubview(activityIndicator)
alert.view.heightAnchor.constraint(equalToConstant: 95).isActive = true

activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor, constant: 0).isActive = true
activityIndicator.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -20).isActive = true

present(alert, animated: true)