How to display activity indicator in center of UIAlertController?

后端 未结 13 983
猫巷女王i
猫巷女王i 2020-12-08 05:18

I currently have a UIAlertController being displayed on the screen. The view of the alert should only display 2 elements, a title and a UIActivityIndicato

相关标签:
13条回答
  • 2020-12-08 05:21

    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

    0 讨论(0)
  • 2020-12-08 05:21

    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

    Bug Reporter

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

    0 讨论(0)
  • 2020-12-08 05:27

    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:

    0 讨论(0)
  • 2020-12-08 05:29

    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)
    
    0 讨论(0)
  • 2020-12-08 05:32

    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)
    
    0 讨论(0)
  • 2020-12-08 05:32

    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
    }
    
    0 讨论(0)
提交回复
热议问题