15

我将我的应用程序移植到 iOS 8.0 并注意到 UIAlertView 已被弃用。

所以我改变了一些东西来使用 UIAlertController。在大多数情况下都有效。

除了,当我的应用程序打开时,它会进行多次检查以向用户报告各种状态......

例如..“警告,您尚未设置 X,需要在完成项目之前进行 Y”和“警告,您使用的是 beta 版本,不依赖结果”等...(这些只是示例!)

在 UIAlertView 下,我会(比如说)同时获得两个警报框,用户必须点击两次才能关闭这两个警报框……但它们都会出现。

在 UIAlertController 下,使用下面的代码呈现“一般”警报,我只收到一条警报消息和一条控制台消息:

警告:尝试在 TestViewController 上呈现 UIAlertController: 0x13f667bb0: 0x13f63cb40 已经呈现 UIAlertController: 0x13f54edf0

因此,尽管上述示例可能不是一个很好的示例,但我认为有时可能由于操作应用程序时的“事件”而需要呈现多个全局警报。在旧的 UIAlertView 下,它们会出现,但似乎不会出现在 UIAlertController 下。

谁能建议如何使用 UIAlertController 来实现这一点?

谢谢

+(void)presentAlert:(NSString*)alertMessage withTitle:(NSString*)title
{
    UIAlertController *alertView = [UIAlertController
                                    alertControllerWithTitle:title
                                    message:alertMessage
                                    preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* ok = [UIAlertAction
                         actionWithTitle:kOkButtonTitle
                         style:UIAlertActionStyleDefault
                         handler:^(UIAlertAction * action)
                         {
                             //Do some thing here
                             [alertView dismissViewControllerAnimated:YES completion:nil];
                         }];

    [alertView addAction:ok];

    UIViewController *rootViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
    [rootViewController presentViewController:alertView animated:YES completion:nil];

编辑:我注意到在 iOS8 上,连续显示两个 AlertView,它们是“排队”并按顺序出现,而在 iOS7 中,它们同时出现。似乎 Apple 已更改 UIAlertView 以对多个实例进行排队。有没有办法用 UIAlertController 做到这一点,而无需继续使用(已弃用但已修改)的 UIAlertView ???

4

10 回答 10

5

I am also facing some problemls with UIAlertController when it comes to present it. Right now the only solution I can suggest is to present alert controller from top most presentedViewContrller if any or window's rootViewController.

UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;

while(presentingViewController.presentedViewController != nil)
{
    presentingViewController = presentingViewController.presentedViewController;
}

[presentingViewController presentViewController:alertView animated:YES completion:nil];

The warning you are getting is not just limited to UIAlertController. A view controller(window's rootViewController in your case) can present only one view controller at a time.

于 2014-12-01T08:27:12.017 回答
4

我完全理解这里的问题,并通过 UIAlertController 类别提出了以下解决方案。它的设计是,如果一个警报已经出现,它会延迟显示下一个警报,直到它收到第一个警报已被解除的通知。

UIAlertController+MH.h

#import <UIKit/UIKit.h>

@interface UIAlertController (MH)

// Gives previous behavior of UIAlertView in that alerts are queued up.
-(void)mh_show;

@end

UIAlertController+MH.m

@implementation UIAlertController (MH)

// replace the implementation of viewDidDisappear via swizzling.
+ (void)load {
    static dispatch_once_t once_token;
    dispatch_once(&once_token,  ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(viewDidDisappear:));
        Method extendedMethod = class_getInstanceMethod(self, @selector(mh_viewDidDisappear:));
        method_exchangeImplementations(originalMethod, extendedMethod);
    });
}

-(UIWindow*)mh_alertWindow{
    return objc_getAssociatedObject(self, "mh_alertWindow");
}

-(void)mh_setAlertWindow:(UIWindow*)window{
    objc_setAssociatedObject(self, "mh_alertWindow", window, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(void)mh_show{
    void (^showAlert)() = ^void() {
        UIWindow* w = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        // we need to retain the window so it can be set to hidden before it is dealloced so the observation fires.
        [self mh_setAlertWindow:w];
        w.rootViewController = [[UIViewController alloc] init];
        w.windowLevel = UIWindowLevelAlert;
        [w makeKeyAndVisible];
        [w.rootViewController presentViewController:self animated:YES completion:nil];
    };

    // check if existing key window is an alert already being shown. It could be our window or a UIAlertView's window.
    UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow;
    if(keyWindow.windowLevel == UIWindowLevelAlert){
        // if it is, then delay showing this new alert until the previous has been dismissed.
        __block id observer;
        observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIWindowDidBecomeHiddenNotification object:keyWindow queue:nil usingBlock:^(NSNotification * _Nonnull note) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
            showAlert();
        }];
    }else{
        // otherwise show the alert immediately.
        showAlert();
    }
}

- (void)mh_viewDidDisappear:(BOOL)animated {
    [self mh_viewDidDisappear:animated]; // calls the original implementation
    [self mh_alertWindow].hidden = YES;
}

@end

此代码甚至可以处理通过已弃用的 UIAlertView 呈现先前警报的情况,即它也等待它完成。

要对此进行测试,您需要做的就是使用两个不同的警报控制器连续调用 show 两次,您将看到第二个等待直到第一个被关闭后再呈现。

于 2016-02-04T21:02:55.597 回答
2

这个解决方案对我有用。我有一个 AlertManager 正在处理一个接一个出现的警报队列。要知道何时显示另一个警报,我正在扩展 UIAlertController 并覆盖其 viewDidDisappear 函数。

此解决方案必须在 viewDidAppear 之后使用。如果不是,则不会显示警报。链条将被破坏,并且不会出现进一步的警报。另一种选择是稍后尝试挂起警报或丢弃它,这将为将来的警报释放队列。

/// This class presents one alert after another.
/// - Attention:  If one of the alerts are not presented for some reason (ex. before viewDidAppear), it will not disappear either and the chain will be broken. No further alerts would be shown.
class AlertHandler {
    private var alertQueue = [UIAlertController]()
    private var alertInProcess: UIAlertController?

    // singleton
    static let sharedAlerts = AlertHandler()
    private init() {}

    func addToQueue(alert: UIAlertController) {
        alertQueue.append(alert)
        handleQueueAdditions()
    }

    private func handleQueueAdditions() {
        if alertInProcess == nil {
            let alert = alertQueue.removeFirst()
            alertInProcess = alert
            UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
        }
    }

    private func checkForNextAlert(alert: UIAlertController) {
        if alert === alertInProcess {
            if alertQueue.count > 0 {
                let alert = alertQueue.removeFirst()
                alertInProcess = alert
                UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
            } else {
                alertInProcess = nil
            }
        }
    }
}

extension UIAlertController {
    public override func viewDidDisappear(animated: Bool) {
        AlertHandler.sharedAlerts.checkForNextAlert(self)
    }
}

AlertHandler.sharedAlerts.addToQueue(alert:)
于 2016-09-15T07:27:37.230 回答
1

我对这里的任何解决方案都不满意,因为它们需要太多的手动工作或需要调配,而我在生产应用程序中对此并不满意。我创建了一个新类(GitHub),它从此处的其他答案中获取元素。

警报队列.h

//
//  AlertQueue.h
//
//  Created by Nick Brook on 03/02/2017.
//  Copyright © 2018 Nick Brook. All rights reserved.
//

#import <UIKit/UIKit.h>

@protocol AlertQueueAlertControllerDelegate;

@interface AlertQueueAlertController : UIAlertController

/**
 The alert delegate
 */
@property(nonatomic, weak, nullable) id<AlertQueueAlertControllerDelegate> delegate;

/**
 Any relevant user info for this alert
 */
@property(nonatomic, readonly, nullable) NSDictionary * userInfo;

/**
 The view controller that requested the alert be displayed, if one was passed when adding to the queue
 */
@property(nonatomic, weak, readonly, nullable) UIViewController *presentingController;

/**
 Create an alert with a title, message and user info

 @param title The title for the alert
 @param message The message for the alert
 @param userInfo The user info dictionary
 @return An alert
 */
+ (nonnull instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message userInfo:(nullable NSDictionary *)userInfo;

/**
 - Warning: This method is not available on this subclass. Use +alertControllerWithTitle:message:userInfo: instead.
 */
+ (nonnull instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle NS_UNAVAILABLE;

@end

@interface AlertQueue : NSObject

/**
 The queue of alerts including the currently displayed alerts. The current alert is at index 0 and the next alert to be displayed is at 1. Alerts are displayed on a FIFO basis.
 */
@property(nonatomic, readonly, nonnull) NSArray<AlertQueueAlertController *> *queuedAlerts;

/**
 The currently displayed alert
 */
@property(nonatomic, readonly, nullable) AlertQueueAlertController *displayedAlert;

+ (nonnull instancetype)sharedQueue;

/**
 Display an alert, or add to queue if an alert is currently displayed

 @param alert The alert to display
 */
- (void)displayAlert:(nonnull AlertQueueAlertController *)alert;

/**
 Display an alert, or add to queue if an alert is currently displayed

 @param alert The alert to display
 @param userInfo Any relevant information related to the alert for later reference. If a userinfo dictionary already exists on the alert, the dictionaries will be merged with the userinfo here taking precedence on conflicting keys.
 */
- (void)displayAlert:(nonnull AlertQueueAlertController *)alert userInfo:(nullable NSDictionary *)userInfo;

/**
 Display an alert, or add to queue if an alert is currently displayed

 @param alert The alert to display
 @param viewController The presenting view controller, stored on the alert for future reference
 @param userInfo Any relevant information related to the alert for later reference. If a userinfo dictionary already exists on the alert, the dictionaries will be merged with the userinfo here taking precedence on conflicting keys.
 */
- (void)displayAlert:(nonnull AlertQueueAlertController *)alert fromController:(nullable UIViewController *)viewController userInfo:(nullable NSDictionary *)userInfo;

/**
 Cancel a displayed or queued alert

 @param alert The alert to cancel
 */
- (void)cancelAlert:(nonnull AlertQueueAlertController *)alert;

/**
 Cancel all alerts from a specific view controller, useful if the controller is dimissed.

 @param controller The controller to cancel alerts from
 */
- (void)invalidateAllAlertsFromController:(nonnull UIViewController *)controller;

@end

@protocol AlertQueueAlertControllerDelegate <NSObject>

/**
 The alert was displayed

 @param alertItem The alert displayed
 */
- (void)alertDisplayed:(nonnull AlertQueueAlertController *)alertItem;

/**
 The alert was dismissed

 @param alertItem The alert dismissed
 */
- (void)alertDismissed:(nonnull AlertQueueAlertController *)alertItem;

@end

警报队列.m

//
//  AlertQueue.m
//  Nick Brook
//
//  Created by Nick Brook on 03/02/2017.
//  Copyright © 2018 Nick Brook. All rights reserved.
//

#import "AlertQueue.h"

@protocol AlertQueueAlertControllerInternalDelegate
@required
- (void)alertQueueAlertControllerDidDismiss:(AlertQueueAlertController *)alert;

@end

@interface AlertQueueAlertController()

@property(nonatomic, strong, nullable) NSDictionary * userInfo;
@property (nonatomic, weak, nullable) id<AlertQueueAlertControllerInternalDelegate> internalDelegate;
@property(nonatomic, weak) UIViewController *presentingController;

@end

@implementation AlertQueueAlertController

+ (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message userInfo:(NSDictionary *)userInfo {
    AlertQueueAlertController *ac = [super alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    ac.userInfo = userInfo;
    return ac;
}

- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
    [super dismissViewControllerAnimated:flag completion:completion];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.internalDelegate alertQueueAlertControllerDidDismiss:self];
}

@end

@interface AlertQueue() <AlertQueueAlertControllerInternalDelegate>

@property(nonatomic, strong, nonnull) NSMutableArray<AlertQueueAlertController *> *internalQueuedAlerts;
@property(nonatomic, strong, nullable) AlertQueueAlertController *displayedAlert;
@property(nonatomic, strong) UIWindow *window;
@property(nonatomic, strong) UIWindow *previousKeyWindow;

@end

@implementation AlertQueue

+ (nonnull instancetype)sharedQueue {
    static AlertQueue *sharedQueue = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedQueue = [AlertQueue new];
    });
    return sharedQueue;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.window = [UIWindow new];
        self.window.windowLevel = UIWindowLevelAlert;
        self.window.backgroundColor = nil;
        self.window.opaque = NO;
        UIViewController *rvc = [UIViewController new];
        rvc.view.backgroundColor = nil;
        rvc.view.opaque = NO;
        self.window.rootViewController = rvc;
        self.internalQueuedAlerts = [NSMutableArray arrayWithCapacity:1];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeHidden:) name:UIWindowDidBecomeHiddenNotification object:nil];
    }
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)windowDidBecomeHidden:(NSNotification *)notification {
    [self displayAlertIfPossible];
}

- (void)alertQueueAlertControllerDidDismiss:(AlertQueueAlertController *)alert {
    if(self.displayedAlert != alert) { return; }
    self.displayedAlert = nil;
    [self.internalQueuedAlerts removeObjectAtIndex:0];
    if([alert.delegate respondsToSelector:@selector(alertDismissed:)]) {
        [alert.delegate alertDismissed:(AlertQueueAlertController * _Nonnull)alert];
    }
    [self displayAlertIfPossible];
}

- (void)displayAlertIfPossible {
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    if(self.displayedAlert != nil || (keyWindow != self.window && keyWindow.windowLevel >= UIWindowLevelAlert)) {
        return;
    }
    if(self.internalQueuedAlerts.count == 0) {
        self.window.hidden = YES;
        [self.previousKeyWindow makeKeyWindow];
        self.previousKeyWindow = nil;
        return;
    }
    self.displayedAlert = self.internalQueuedAlerts[0];
    self.window.frame = [UIScreen mainScreen].bounds;
    if(!self.window.isKeyWindow) {
        self.previousKeyWindow = UIApplication.sharedApplication.keyWindow;
        [self.window makeKeyAndVisible];
    }
    [self.window.rootViewController presentViewController:(UIViewController * _Nonnull)self.displayedAlert animated:YES completion:nil];
    if([self.displayedAlert.delegate respondsToSelector:@selector(alertDisplayed:)]) {
        [self.displayedAlert.delegate alertDisplayed:(AlertQueueAlertController * _Nonnull)self.displayedAlert];
    }
}

- (void)displayAlert:(AlertQueueAlertController *)alert {
    [self displayAlert:alert userInfo:nil];
}

- (void)displayAlert:(AlertQueueAlertController *)alert userInfo:(NSDictionary *)userInfo {
    [self displayAlert:alert fromController:nil userInfo:userInfo];
}

- (void)displayAlert:(AlertQueueAlertController *)alert fromController:(UIViewController *)viewController userInfo:(NSDictionary *)userInfo {
    if(alert.preferredStyle != UIAlertControllerStyleAlert) { // cannot display action sheets
        return;
    }
    alert.internalDelegate = self;
    if(userInfo) {
        if(alert.userInfo) {
            NSMutableDictionary *d = alert.userInfo.mutableCopy;
            [d setValuesForKeysWithDictionary:userInfo];
            alert.userInfo = d;
        } else {
            alert.userInfo = userInfo;
        }
    }
    alert.presentingController = viewController;
    [self.internalQueuedAlerts addObject:alert];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self displayAlertIfPossible];
    });
}

- (void)cancelAlert:(AlertQueueAlertController *)alert {
    if(alert == self.displayedAlert) {
        [self.displayedAlert dismissViewControllerAnimated:YES completion:nil];
    } else {
        [self.internalQueuedAlerts removeObject:alert];
    }
}

- (void)invalidateAllAlertsFromController:(UIViewController *)controller {
    NSArray<AlertQueueAlertController *> *queuedAlerts = [self.internalQueuedAlerts copy];
    for(AlertQueueAlertController *alert in queuedAlerts) {
        if(alert.presentingController == controller) {
            [self cancelAlert:alert];
        }
    }
}

- (NSArray<AlertQueueAlertController *> *)queuedAlerts {
    // returns new array so original can be manipulated (alerts cancelled) while enumerating
    return [NSArray arrayWithArray:_internalQueuedAlerts];
}

@end

示例用法

AlertQueueAlertController *ac = [AlertQueueAlertController alertControllerWithTitle:@"Test1" message:@"Test1" userInfo:nil];
[ac addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    NSLog(@"Alert!");
}]];
[[AlertQueue sharedQueue] displayAlert:ac fromController:self userInfo:nil];
于 2017-02-04T00:34:04.303 回答
0

这可以通过在 UIAlertcontroller 的操作处理程序中使用检查标志来解决。

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
_isShowAlertAgain = YES;
[self showAlert];
}

- (void)showAlert {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"This is Alert" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
    [alertController dismissViewControllerAnimated:YES completion:nil];
    if (_isShowAlertAgain) {
        _isShowAlertAgain = NO;
        [self showAlert];
    }
}];
UIAlertAction *cancelButton = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
    [alertController dismissViewControllerAnimated:YES completion:nil];
}];
[alertController addAction:okButton];
[alertController addAction:cancelButton];
[self presentViewController:alertController animated:YES completion:nil];
}
于 2015-03-05T21:49:22.957 回答
0

我创建了一个带有演示的 Github 项目MAAlertPresenter来处理这个问题。您可以使用它来一一呈现 UIAlertController ,只需几行更改。

于 2016-08-10T13:50:56.353 回答
0

从 UIAlertView 切换到 UIAlertController 后,我也遇到了同样的问题。我不喜欢 Apple 的政策,因为“消息框”在 BIG BANG 的几乎所有 SO 中一直是可堆叠的。我同意并发警报并不是一个很好的用户体验,有时它是糟糕设计的结果,但有时(例如 UILocalNotification 或类似的东西)它们可能会发生,我害怕我会因为我的应用程序而失去一个重要的阻塞警报刚刚收到通知。

也就是说,这是我的 2cents 解决方案,一个递归函数,如果发件人没有 presentViewController,它会尝试在发送者上显示 alertcontroller,否则它会尝试在presentedViewController 上显示 alertcontroller 等等......如果你同时触发更多 AlertController 因为您无法从正在呈现的控制器中呈现视图控制器,但它应该在任何其他合理的工作流程中工作。

+ (void)presentAlert:(UIAlertController *)alert withSender:(id)sender
{
    if ([sender presentedViewController])
    {
        [self presentAlert:alert withSender: [sender presentedViewController]];
    }
    else
    {
        [sender presentViewController:alert animated:YES completion:nil];
    }
}
于 2017-09-26T20:54:34.657 回答
0

这似乎是一个老问题,但仍然发布它可能对寻找这个的人有用,尽管 Apple 不建议堆叠多个警报,这就是他们弃用 UIAlertView 到 UIAlertController 实现的原因。

我为 UIAlertAction 创建了一个 AQAlertAction 子类。您可以将它用于交错警报,用法与您使用 UIAlertAction 相同。您需要做的就是在您的项目中导入AQMutiAlertFramework,或者您也可以包含类(请参阅示例项目)。在内部,它使用二进制信号量来错开警报,直到用户处理与显示的当前警报相关联的操作。请让我知道这对你有没有用。

于 2016-09-15T04:11:14.500 回答
0

如果您所需要的只是简单地读取和关闭的简单信息警报,那么这就是我刚刚想出的(它并不完全是花哨的高级代码,并且涉及“耦合”,但是,嘿......它很短/simple,在某些情况下可能有用):

ReadOnlyMessageQueue.swift:

import Foundation

protocol ReadOnlyMessageQueueDelegate: class {
    func showAlert(message: String, title: String)
}

class ReadOnlyMessageQueue {

    weak var delegate: ReadOnlyMessageQueueDelegate?

    private var queue = [(message: String, title: String)]()

    public func addAlertMessageToQueue(message: String, title: String) {
        print("MQ.add: \(message)")
        queue.append((message,title))
        if queue.count == 1 {
            delegate?.showAlert(message: message, title: title)
        }
    }

    public func alertWasDismissedInParentVC() {
        print("MQ.wasDissmissed")
        if queue.count > 1 {
            delegate?.showAlert(message: queue[1].message, title: self.queue[1].title)
            self.queue.remove(at: 0)
        } else if queue.count == 1 {
            self.queue.remove(at: 0)
        }
    }

}

ViewController.swift:

import UIKit

class ViewController: UIViewController, ReadOnlyMessageQueueDelegate {

    let messageQueue = ReadOnlyMessageQueue()

    override func viewDidLoad() {
        super.viewDidLoad()
        messageQueue.delegate = self
    }

    override func viewDidAppear(_ animated: Bool) {
        for i in 4...20 {
            print("VC.adding: \(i)")
            messageQueue.addAlertMessageToQueue(message: String(i), title: String(i))
        }
    }

    func showAlert(message: String, title: String) {
        print("VC.showing: \(message)")
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
            _ in
            self.messageQueue.alertWasDismissedInParentVC()
            }
        ))
        self.present(alert, animated: false)
    }

}
于 2019-03-31T06:05:13.280 回答
-1

我用这行代码解决了这个问题:

alert.modalTransitionStyle=UIModalPresentationOverCurrentContext;
于 2015-01-22T15:41:52.423 回答