我正在将我的应用移植到iOS 8.0,并注意到UIAlertView已被弃用。
所以我改变了使用UIAlertController的东西。哪种方法适用于大多数情况。
除此之外,当我的应用程序打开时,它会进行多次检查以向用户报告各种状态...
例如..“警告,你没有设置X并且在完成项目之前需要做Y”和“警告,你使用的是测试版并且不依赖于结果”等...(这些只是实例!)
在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 ???
答案 0 :(得分:4)
当谈到呈现它时,我也面临着一些与UIAlertController有关的问题。 现在,我可以建议的唯一解决方案是从最顶层的presentContrller呈现警报控制器(如果有的话)或者是窗口的rootViewController。
UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
while(presentingViewController.presentedViewController != nil)
{
presentingViewController = presentingViewController.presentedViewController;
}
[presentingViewController presentViewController:alertView animated:YES completion:nil];
您获得的警告不仅限于UIAlertController。视图控制器(在您的情况下是窗口的rootViewController)一次只能显示一个视图控制器。
答案 1 :(得分: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,你会看到第二个等待直到第一个被释放才被呈现出来。
答案 2 :(得分: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:)
答案 3 :(得分:1)
我对这里的任何解决方案都不满意,因为他们需要太多的手动工作或需要在生产应用程序中不习惯的调配。我创建了一个新类(GitHub),它从其他答案中获取元素。
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
AlertQueue.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];
答案 4 :(得分:0)
这可以通过在UIAlertcontroller的动作处理程序中使用check标志来解决。
- (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];
}
答案 5 :(得分:0)
我创建了一个Github项目MAAlertPresenter,其中包含一个处理此问题的演示。您可以使用它逐个呈现UIAlertController,只需几行更改。
答案 6 :(得分:0)
这似乎是一个老问题,但仍然发布,因为它可能对寻找此事的人有用,虽然Apple并不建议多个警报堆叠,这就是为什么他们将此UIAlertView弃用到UIAlertController实现。
我为UIAlertAction创建了一个AQAlertAction子类。您可以将它用于交错警报,其用法与使用UIAlertAction相同。您需要做的就是在项目中导入AQMutiAlertFramework,或者也可以包含类(请参考Sample project)。 内部它使用二进制信号量来交错警报,直到用户处理与当前警报相关联的操作。让我知道它是否适合您。
答案 7 :(得分:0)
从UIAlertView切换到UIAlertController后,我也遇到了同样的问题。我不喜欢Apple政策,因为&#34; Message Boxes&#34;几乎每一个来自BIG BANG的SO都可以堆叠。 我同意并发警报不是一个很好的用户体验,有时它是一个糟糕的设计的结果,但有时(前UILocalNotification或类似的东西)他们只是可能发生,它很害怕,我可以松动一个重要的阻止警报只是因为我的应用刚刚收到通知。
那就是说,这是我的2cents解决方案,一个递归函数,如果发送者没有presentViewController,它试图在发送者上呈现alertcontroller,否则它会尝试在presentViewController上呈现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];
}
}
答案 8 :(得分:0)
如果您所需要的只是简单的信息警报,这些警报只需读取并关闭即可,那么这就是我刚想到的(它并不是花哨的高级代码,并且存在“耦合”涉及,但是,嘿...它简短/简单,在某些情况下可能有用):
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)
}
}
答案 9 :(得分:-1)
我用这行代码解决了这个问题:
alert.modalTransitionStyle=UIModalPresentationOverCurrentContext;