How to display activity indicator in center of UIAlertController?
Asked Answered
L

15

27

I currently have a UIAlertController being displayed on the screen. The view of the alert should only display 2 elements, a title and a UIActivityIndicatorView in the center of the alert. Below is the function that displays the alert and its elements.

func displaySignUpPendingAlert() -> UIAlertController {
        //Create the UIAlertController
        let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)
        //Create the activity indicator to display in it.
        let indicator = UIActivityIndicatorView(frame: CGRectMake(pending.view.frame.width / 2.0, pending.view.frame.height / 2.0, 20.0, 20.0))
        indicator.center = CGPointMake(pending.view.frame.width / 2.0, pending.view.frame.height / 2.0)
        //Add the activity indicator to the alert's view
        pending.view.addSubview(indicator)
        //Start animating
        indicator.startAnimating()

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

However, the activity indicator doesn't display in the center of the view, in fact it displays in the bottom right of the screen, far off of the view. What is the reason for this?

EDIT: I understand that I can hardcode numbers for the indicator's position, but I want the alert to work on multiple devices with multiple screen sizes and orientations.

Linnie answered 20/11, 2014 at 6:50 Comment(1)
Related: How do I put a UIActivityIndicatorView in a UIAlertController?Putative
K
39

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)
Keyser answered 20/11, 2014 at 7:55 Comment(10)
Thanks for your help.But i am having trouble that your activator is in the center with alert message title like this -> puu.sh/ilFpD/d8e1f98d8f.png. is there any way to put the activator to down position of the alert title please?thank you.Omniscience
Swipesight,it show perfectly like i wanted now sir.Thank you for your answer.Omniscience
You can also help me here,if u dont mind.I am having some problem with calling my custom alert from another view controller.Here is my link : #30837738Omniscience
Using the @62Shark solution, the UIActivityIndicatorView is flush to the left of the iPad screen and slightly beneath the UIAlertController. i.imgur.com/ehy8BdD.pngPutative
I think u set another activator indicator at the storyboard.When u use this alert,there is no need to set another activator indicator in the storyboard of your view controller.Omniscience
@62Shark I didn't use a storyboard; I did this all programmatically. Here's my full question about this: #31408819Putative
If you add a few \n newlines to the end of the message of the alert controller, it will expand the height of it enough so that the spinner no longer obscures the text but appears below the title and message as it should.Bikol
How to dismiss it?Envious
This is a bad answer that is not at all future proof. Do what @catlan suggests: https://mcmap.net/q/456215/-how-to-display-activity-indicator-in-center-of-uialertcontrollerSyllogize
@FaizanMubasher for dismiss an UIAlertController, you only need to write this: yourAlert.dismiss(animated: true, completion: nil)Kings
A
29

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

Arguseyed answered 11/9, 2015 at 8:4 Comment(3)
Thank you, I love Swift, but I still write most of my apps in Objective-C, this is very helpful.Fredra
Can you explain the constraints please?Hilariohilarious
work perfect. this code to dismiss after data loading is done. [self dismissViewControllerAnimated:NO completion:nil];Tempered
F
24

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.

Freund answered 11/2, 2018 at 9:23 Comment(5)
This is the only right answer. Don't hack private view hierarchies. It's funny how SO is now a bad answer marked "accepted" and then 500 "me too" clones in different versions of Swift and Objective-C, but only @Freund is illustrating how the hacks will break in future versions of UIKit.Syllogize
@Syllogize more funny is the fact you praise an answer in a different programming language than OP asks.Aquamarine
I've translated this code in Swift hereCrossfertilize
The Swift code is wrong and all you will see is a dimmed view. The guard statements should be separated and each should be inside the isPresentation is else. fromView view always be nil when isPresentation and vice-versa.Paralyse
Thank you for this answer! Seems it's a good approach ❤️ But current example (Swift) is not working for me. Decided to create a simple UIViewController with UIActivityIndicatorView and use default transition style.Hinkel
U
10

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)
Unitary answered 29/5, 2019 at 10:53 Comment(0)
G
6

I have to implement NSLayoutConstraints to put the UIActivityIndicatorView on the center of the UIAlertController

For Swift:

let loadingAlertController: UIAlertController = UIAlertController(title: "Loading", message: nil, preferredStyle: .alert)
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .gray)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
    
loadingAlertController.view.addSubview(activityIndicator)
    
let xConstraint: NSLayoutConstraint = NSLayoutConstraint(item: activityIndicator, attribute: .centerX, relatedBy: .equal, toItem: loadingAlertController.view, attribute: .centerX, multiplier: 1, constant: 0)
let yConstraint: NSLayoutConstraint = NSLayoutConstraint(item: activityIndicator, attribute: .centerY, relatedBy: .equal, toItem: loadingAlertController.view, attribute: .centerY, multiplier: 1.4, constant: 0)
    
NSLayoutConstraint.activate([ xConstraint, yConstraint])
activityIndicator.isUserInteractionEnabled = false
activityIndicator.startAnimating()
    
let height: NSLayoutConstraint = NSLayoutConstraint(item: loadingAlertController.view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 80)
loadingAlertController.view.addConstraint(height)

self.present(loadingAlertController, animated: true, completion: nil)

Result:

enter image description here

Glassworker answered 13/11, 2016 at 3:50 Comment(0)
O
1

For those like me who prefer UIActivityIndicatorView aligned at the left of the UIAlertController.title, this is my solution in Swift working for all devices:

let alert = UIAlertController(title: NSLocalizedString("Authenticating...", comment: "Authenticating"), message: nil, preferredStyle: .Alert);
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
activityIndicator.frame = activityIndicator.frame.rectByOffsetting(dx: 8, dy: (alert.view.bounds.height - activityIndicator.frame.height)/2);
activityIndicator.autoresizingMask = .FlexibleRightMargin | .FlexibleTopMargin | .FlexibleBottomMargin
activityIndicator.color = themeManager().currentTheme.navigationBarTintColor;
activityIndicator.startAnimating();
alert.view.addSubview(activityIndicator);
self.presentViewController(progressAlert, animated: true, completion: nil);

However, to align the UIActivityIndicatorView in the view center you can change as follows:

activityIndicator.center = CGPoint(x: (alert.view.bounds.width)/2, y: (alert.view.bounds.height)/2)
activityIndicator.autoresizingMask = .FlexibleLeftMargin | .FlexibleRightMargin | .FlexibleTopMargin | .FlexibleBottomMargin
Oecology answered 10/6, 2015 at 18:30 Comment(0)
R
1

Apple does not encourage directly subclassing UIAlertController so I made a class that displays UIAlertController with centered UIActivityIndicator and handles the cancel condition with a class protocol.

import Foundation
import UIKit

protocol BusyAlertDelegate {
    func didCancelBusyAlert()
}


class BusyAlert {

   var busyAlertController: UIAlertController?
   var presentingViewController: UIViewController?
   var activityIndicator: UIActivityIndicatorView?
   var delegate:BusyAlertDelegate?

   init (title:String, message:String, presentingViewController: UIViewController) {
       busyAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
       busyAlertController!.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel Button"), style: UIAlertActionStyle.Cancel, handler:{(alert: UIAlertAction!) in
            delegate?.didCancelBusyAlert()
    }))
        self.presentingViewController = presentingViewController
        activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
        busyAlertController!.view.addSubview(activityIndicator!)
    }

    func display() {
        dispatch_async(dispatch_get_main_queue(), {
               self.presentingViewController!.presentViewController(self.busyAlertController!, animated: true, completion: {
            self.activityIndicator!.translatesAutoresizingMaskIntoConstraints = false
               self.busyAlertController!.view.addConstraint(NSLayoutConstraint(item: self.activityIndicator!, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.busyAlertController!.view, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0))
            self.busyAlertController!.view.addConstraint(NSLayoutConstraint(item: self.activityIndicator!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.busyAlertController!.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
            self.activityIndicator!.startAnimating()

        })
    })

}

func dismiss() {
    dispatch_async(dispatch_get_main_queue(), {
        self.busyAlertController?.dismissViewControllerAnimated(true, completion: nil)
    })
}

}

I recommend using lazy var to initialize the class.

lazy var busyAlertController: BusyAlert = {
        let busyAlert = BusyAlert(title: "Lengthy Task", message: "Please     wait...", presentingViewController: self)
        busyAlert.delegate = self
        return busyAlert
        }()

Here is a link to sample code: https://github.com/cgilleeny/BusyAlertExample.git

Reimburse answered 12/10, 2015 at 20:7 Comment(0)
B
1

It's this simple.

fully tested ...

extension UIViewController {
    func verySimpleSpinner() -> UIAlertController {
        let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
        let spinner = UIActivityIndicatorView(style: .medium)
        spinner.startAnimating()

        alert.view.addSubview(spinner)
        spinner.bindEdgesToSuperview()

        present(alert, animated: true, completion: nil)
        return alert
    }
}

It's impossible to write iOS apps unless you have a simple .bindEdgesToSuperview() call -

extension UIView {
    func bindEdgesToSuperview() {
        guard let s = superview else { preconditionFailure("flop") }
        translatesAutoresizingMaskIntoConstraints = false
        leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: s.topAnchor).isActive = true
        bottomAnchor.constraint(equalTo: s.bottomAnchor).isActive = true
    }
}

If you want text

If you do also want text, use the excellent code from @magnuskahr. Modernized:

fully tested ...

extension UIView {
    func verySimpleSpinner() -> UIAlertController {
        let alert = UIAlertController(title: "", message: "Connecting...", preferredStyle: .alert)
        let spinner = UIActivityIndicatorView(style: .medium)
        alert.view.addSubview(spinner)
        
        alert.view.heightAnchor.constraint(equalToConstant: 95).isActive = true
        spinner.translatesAutoresizingMaskIntoConstraints = false
        spinner.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor, constant: 0).isActive = true
        spinner.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -20).isActive = true
        
        spinner.startAnimating()
        present(alert, animated: true, completion: nil)
        return alert
    }
}

How to use

In any view controller:

    let spinny = verySimpleSpinner()

when the connection/etc has finished:

    spinny.dismiss(animated: true)
Bal answered 30/5, 2022 at 16:11 Comment(0)
P
1

If you want a ActivityIndicatorView only alert then try this.

func presentLoader() {
    let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert)
    let activityIndicator = UIActivityIndicatorView(style: .large)
    activityIndicator.translatesAutoresizingMaskIntoConstraints = false
    activityIndicator.isUserInteractionEnabled = false
    activityIndicator.color = .blue
    activityIndicator.startAnimating()
            
    alert.view.addSubview(activityIndicator)
    
    NSLayoutConstraint.activate([
        alert.view.heightAnchor.constraint(equalToConstant: 95),
        alert.view.widthAnchor.constraint(equalToConstant: 95),
        activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor),
        activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor)
    ])
    
    present(alert, animated: true)
}

Result:

enter image description here

Photolysis answered 1/2, 2023 at 13:2 Comment(0)
E
0

In swift:

activityIndicator.center = self.view.center

If you have a tool bar or a navController you might want to shift the point but otherwise, center is center...

If you still have issues, perhaps this tutorial would help. If you are trying to center it in a table view controller, this answer might help.

Expressway answered 27/9, 2015 at 10:15 Comment(0)
G
-1

Converted @petesalt's answer to Swift 3:

let pending = UIAlertController(title: "Saving, please wait...", message: nil, preferredStyle: .alert)

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

let views = ["pending" : pending.view, "indicator" : indicator]

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

indicator.isUserInteractionEnabled = false
indicator.startAnimating()

self.present(pending, animated: true, completion: nil)
Gunpaper answered 28/5, 2017 at 11:16 Comment(0)
A
-1

How about this way for Swift 3 and higher:

func showActivityIndiactorViewController(title: String) -> UIAlertController {
    let pending = UIAlertController(title: "", message: nil, preferredStyle: .alert)
    let heightConstraint:NSLayoutConstraint = NSLayoutConstraint(item: pending.view, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: self.view.frame.height * 0.10)
    pending.view.addConstraint(heightConstraint)

    let label = UILabel()
    label.text = title
    label.textColor = UIColor.black
    label.sizeToFit()

    let space = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 8))

    let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
    indicator.isUserInteractionEnabled = false
    indicator.startAnimating()

    let width = Int(label.frame.size.width + indicator.frame.size.width + space.frame.size.width)

    let view = UIStackView(arrangedSubviews: [indicator, space, label])
    view.axis = .horizontal
    view.frame = CGRect(x: 20, y: 0, width: width, height: Int(heightConstraint.constant))
    pending.view.addSubview(view)

    let widthConstraint:NSLayoutConstraint = NSLayoutConstraint(item: pending.view, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.greaterThanOrEqual, toItem: view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: CGFloat(width))
    pending.view.addConstraint(widthConstraint)

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

    return pending
}
Ascetic answered 16/6, 2018 at 19:5 Comment(0)
P
-2

Try this:

activityView.center = CGPointMake(self.view.bounds.size.width/2.0, self.view.bounds.size.height / 2.0)

Also you will need to check for landscape mode and reverse width and height.

if(landscapeMode)activityView.center = CGPointMake(self.view.bounds.size.height/2.0, self.view.bounds.size.width / 2.0)

Maybe you can get the alert view position?

alert.view.frame.origin.x
alert.view.frame.origin.y

and use that to place your activity view dynamically ie with the variables?

Of course you might also want to get the size divide by 2 and add that so that its centred as well.

alert.view.frame.size.height
alert.view.frame.size.width
Polaris answered 20/11, 2014 at 7:25 Comment(2)
The alert show up in the center, but the activity indicator is near the bottom of the screen, almost in the corner.Linnie
Oh sorry my bad I thought you meant the alert view was not in the centre. Just centre the activity view to the main screen and it should be on top of it.Polaris
M
-2

Well try this code.

UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
                                    message:@"Creating new user\n\n\n"
                             preferredStyle:UIAlertControllerStyleAlert];

UIActivityIndicatorView *loader = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
loader.center = CGPointMake(130.5, 65.5);
loader.color = [UIColor blackColor];
[loader startAnimating];
[alert.view loader];
[self presentViewController:alert animated:NO completion:nil];
Mignonmignonette answered 20/11, 2014 at 7:26 Comment(3)
The reason I don't want to do it like this is that this uses hardcoded numbers, and I want it to work for multiple screen sizesLinnie
well u can add this [loader setCenter:view.center];Mignonmignonette
Will this compile? Whats this line [alert.view loader] doing?Spongioblast
R
-2

I had the same problem and using frame positioning didn't work for me. Yimin Lin's answer was very close for me, but I just wanted to present an alternative using constraints in non-visual format:

//...
indicator.setTranslatesAutoresizingMaskIntoConstraints(false)
alert.view.addSubview(indicator)

alert.view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: alert.view, attribute: attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0))
alert.view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: alert.view, attribute: attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
//...
Revealment answered 23/10, 2015 at 8:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.