Ipad 并发UIAlertController

Ipad 并发UIAlertController,ipad,ios8,uialertview,uialertcontroller,Ipad,Ios8,Uialertview,Uialertcontroller,我正在将我的应用程序移植到iOS 8.0,注意到UIAlertView已被弃用 因此,我改变了使用UIAlertController的方式。这在大多数情况下都有效 除此之外,当我的应用程序打开时,它会进行多次检查以向用户报告各种状态 例如,“警告,您没有设置X,在完成项目之前需要执行Y”和“警告,您正在使用测试版,不依赖结果”等(这些只是示例!) 在UIAlertView下,我会(说)同时得到两个警报框,用户必须点击两次才能关闭这两个警报框……但它们都出现了 在UIAlertController

我正在将我的应用程序移植到iOS 8.0,注意到UIAlertView已被弃用

因此,我改变了使用UIAlertController的方式。这在大多数情况下都有效

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

例如,“警告,您没有设置X,在完成项目之前需要执行Y”和“警告,您正在使用测试版,不依赖结果”等(这些只是示例!)

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

在UIAlertController下,使用下面的代码显示“常规”警报,我只收到一条警报消息和一条控制台消息:

警告:尝试在TestViewController:0x13f63cb40上显示UIAlertController:0x13f667bb0,该控制器已显示UIAlertController:0x13f54edf0

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

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

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

[presentingViewController presentViewController:alertView animated:YES completion:nil];
有人能建议如何使用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中,它们同时显示。苹果似乎已经改变了UIAlertView,将多个实例排队。有没有一种方法可以在不继续使用(已弃用但已修改)UIAlertController视图的情况下使用UIAlertController执行此操作?

在介绍UIAlertController时,我也面临一些问题。 现在,我能建议的唯一解决方案是从最顶层的PresentedViewController(如果有的话)或窗口的rootViewController显示警报控制器

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

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

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

您得到的警告不仅限于UIAlertController。视图控制器(在您的情况下是窗口的rootViewController)一次只能显示一个视图控制器。

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

alert.modalTransitionStyle=UIModalPresentationOverCurrentContext;

这可以通过在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];
}

我完全理解这里的问题,并通过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
此代码甚至可以处理以前的警报通过不推荐的UIAlertView显示的情况,即它也会等待警报完成


要测试这一点,您需要做的就是使用两个不同的警报控制器连续调用show两次,您将看到第二个警报控制器等待第一个警报控制器被解除,然后才显示。

我创建了一个Github项目,其中包含一个处理此问题的演示。您可以使用它一行一行地显示UIAlertController,只需几行更改。

这似乎是一个老问题,但仍在发布,因为它可能对寻找此功能的人有用,尽管苹果不建议多个警报堆叠,这就是为什么他们不赞成将此UIAlertView从添加到UIAlertController实现的原因


我已经为UIAlertAction创建了一个AQAlertAction子类。您可以将其用于交错警报,其用法与使用UIAlertAction相同。所有您需要做的就是导入到您的项目中,或者您也可以包括类(请参考)在内部,它使用二进制信号量来交错警报,直到用户处理与当前警报相关的操作为止。让我知道它是否适合您。

此解决方案适合我。我有一个AlertManager,它正在处理一个接一个显示的警报队列。为了知道何时显示另一个警报,我扩展了UIAlertController并覆盖了它的ViewDidEnglishe函数

必须在ViewDid出现后使用此解决方案。如果没有,则不会显示警报。链条将断开,不会出现进一步的警报。另一种选择是稍后尝试挂起警报或放弃它,这将释放队列以备将来发出警报

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

我对这里的任何解决方案都不满意,因为它们需要太多的手工工作,或者需要在生产应用程序中进行我不习惯的旋转。我创建了一个新类(),它从其他答案中获取元素

AlertQueue.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

在从UIAlertView切换到UIAlertController之后,我也面临同样的问题。我不喜欢苹果的政策,因为从大爆炸开始,“消息框”几乎一直都是可堆叠的。 我同意并发警报并不是一种很好的用户体验,有时是设计不好的结果,但有时(例如UILocalNotification或诸如此类的东西)它们可能会发生,我担心我会因为我的应用程序刚刚收到通知而丢失一个重要的阻止警报

也就是说,这是我的2cents解决方案,一个递归函数,如果发送方没有presentedViewController,它会尝试在发送方上显示alertcontroller,否则它会尝试在presentedViewController上显示alertcontroller,以此类推。。。如果您同时触发更多AlertController,则此功能不起作用,因为您无法从正在显示的控制器中显示viewcontroller,但它应在任何其他合理的工作流中工作

+ (void)presentAlert:(UIAlertController *)alert withSender:(id)sender
{
    if ([sender presentedViewController])
    {
        [self presentAlert:alert withSender: [sender presentedViewController]];
    }
    else
    {
        [sender presentViewController:alert animated:YES completion:nil];
    }
}
如果您所需要的只是简单的信息警报,这些警报可以简单地读取和取消,那么这就是我刚刚想到的(它不是完全新奇的高级代码,而且涉及到“耦合”,但是,嘿……它很短/简单,在某些情况下可能很有用):

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)
        }
    }

}
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)
    }

}
ViewController.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)
        }
    }

}
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)
    }

}

你有幸找到你问题的答案吗?遗憾的是没有。目前,我坚持己见