ios 如何在 UIAlertController 的中心显示活动指示器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27033466/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 03:42:11  来源:igfitidea点击:

How to display activity indicator in center of UIAlertController?

iosswiftuiviewuiactivityindicatorviewuialertcontroller

提问by Satre

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

我目前有一个UIAlertController正在显示在屏幕上。警报的视图应该只显示 2 个元素,一个标题和一个UIActivityIndicatorView位于警报中心的元素。下面是显示警报及其元素的函数。

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.

编辑:我知道我可以硬编码指标位置的数字,但我希望警报在具有多种屏幕尺寸和方向的多个设备上工作。

回答by ylin0x81

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

创建视图时,请务必设置 frame 属性。

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:

致@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)

回答by Dimitris F.

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

如果有人感兴趣,我将答案转换为目标 C:

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

干杯

回答by catlan

tl;dr

tl;博士

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 hierarchyfor this class is private and must not be modified.

重要的

UIAlertController 类旨在按原样使用,不支持子类化。此类的视图层次结构是私有的, 不得修改

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.

问题不在于 UIAlertController。这是一个非常简单的 UI,一个或两个堆栈视图,具体取决于您是希望 UIActivityIndi​​catorView 留在标题标签上还是在标题下。演示动画正是我们想要的。

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

下面的代码基于 WWDC 会话A Look Inside Presentation Controllers

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 UIViewControllersubclass, season to taste with XIB:

样品UIViewController子类,用 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.

rdar://37433306:制作 UIAlertController 呈现控制器和转换委托公共 API 以启用重用。

回答by magnuskahr

Swift 5.0 solution

Swift 5.0 解决方案

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)

回答by pableiros

I have to implement NSLayoutConstraints to put the UIActivityIndicatorViewon the center of the UIAlertController

我必须实现NSLayoutConstraints 把 放在UIActivityIndicatorView中心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

在此处输入图片说明

回答by Francesco Vadicamo

For those like me who prefer UIActivityIndicatorViewaligned at the left of the UIAlertController.title, this is my solution in Swiftworking for all devices:

对于像我这样喜欢UIActivityIndicatorView在 左侧对齐的人UIAlertController.title,这是我在Swift 中适用于所有设备的解决方案:

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 UIActivityIndicatorViewin the view center you can change as follows:

但是,要UIActivityIndicatorView在视图中心对齐,您可以进行如下更改:

activityIndicator.center = CGPoint(x: (alert.view.bounds.width)/2, y: (alert.view.bounds.height)/2)
activityIndicator.autoresizingMask = .FlexibleLeftMargin | .FlexibleRightMargin | .FlexibleTopMargin | .FlexibleBottomMargin

回答by Caroline Gilleeny

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.

Apple 不鼓励直接子类化 UIAlertController,所以我创建了一个类,用居中的 UIActivityIndi​​cator 显示 UIAlertController 并使用类协议处理取消条件。

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

这是示例代码的链接:https: //github.com/cgilleeny/BusyAlertExample.git

回答by zevij

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 thistutorial would help. If you are trying to center it in a table view controller, thisanswer might help.

如果您仍然有问题,也许教程会有所帮助。如果您试图将它放在表视图控制器中,这个答案可能会有所帮助。

回答by Hajji Daoud

Converted @petesalt's answer to Swift 3:

将@petesalt 的答案转换为 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)

回答by blyabtroi

How about this way for Swift 3 and higher:

对于 Swift 3 及更高版本,这种方式如何:

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
}