Satre
Satre

Reputation: 1744

How to display activity indicator in center of UIAlertController?

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.

Upvotes: 27

Views: 46276

Answers (15)

Sreekuttan
Sreekuttan

Reputation: 1954

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

Upvotes: 1

Fattie
Fattie

Reputation: 12645

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)

Upvotes: 1

pableiros
pableiros

Reputation: 16032

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

Upvotes: 6

catlan
catlan

Reputation: 25246

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.

Upvotes: 25

magnuskahr
magnuskahr

Reputation: 1320

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)

Upvotes: 10

blyabtroi
blyabtroi

Reputation: 2076

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
}

Upvotes: -1

Hajji Daoud
Hajji Daoud

Reputation: 325

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)

Upvotes: -1

linimin
linimin

Reputation: 6377

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)

Upvotes: 39

mad_manny
mad_manny

Reputation: 1091

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))
//...

Upvotes: -2

Caroline Gilleeny
Caroline Gilleeny

Reputation: 19

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

Upvotes: 1

zevij
zevij

Reputation: 2446

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.

Upvotes: 0

Dimitris F.
Dimitris F.

Reputation: 477

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

Upvotes: 29

Francesco Vadicamo
Francesco Vadicamo

Reputation: 5542

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

Upvotes: 1

AMAN77
AMAN77

Reputation: 6302

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

Upvotes: -2

iAnurag
iAnurag

Reputation: 9346

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];

Upvotes: -2

Related Questions