Concurrent UIAlertControllers
Asked Answered
R

10

16

I'm porting my app to iOS 8.0 and notice that UIAlertView is deprecated.

So I've changed things to use a UIAlertController. Which works in most circumstances.

Except, when my app opens, it does several checks to report various states back to the user...

E.g... "Warning, you haven't set X up and need to do Y before completing projects" and "Warning, you are using a beta version and do not rely on results" etc...(these are just examples!)

Under the UIAlertView, I would (say) get two alert boxes concurrently which the user has to tap twice to dismiss both...but they both appear.

Under UIAlertController with the code below to present a 'general' alert, I only get one alert message along with a console message:

Warning: Attempt to present UIAlertController: 0x13f667bb0 on TestViewController: 0x13f63cb40 which is already presenting UIAlertController: 0x13f54edf0

So, although the above probably isn't a good example, I'm thinking there may be times when more than one global alert may need to be presented due to 'events' whilst operating an app. Under the old UIAlertView, they would appear but it seems they will not under a UIAlertController.

Can anyone suggest how this could be achieved with a UIAlertController?

Thanks

+(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];

Edit: I notice that on iOS8, presenting two AlertViews consecutively, they are 'queued' and appear sequentially whereas in iOS7, they appear concurrently. It seems Apple have altered UIAlertView to queue multiple instances. Is there a way to do this with UIAlertController without continuing to use the (deprecated but modified) UIAlertView???

Rubenstein answered 19/9, 2014 at 11:11 Comment(6)
did you have any luck in finding an answer to your question?Engrossing
Sadly no. At the moment, Im sticking with the apparently modified UIAlertView. Time constraints prevent me investigating further.Rubenstein
Do we have any solution for this issue?Manifold
I just posted my solution, check it out below.Illegitimacy
UIAlertController is more and more looking like a less than half-a$$ed job to me.Wroth
Check my answer below (https://mcmap.net/q/102057/-concurrent-uialertcontrollers). I solved this issue and created a Github Project for others.Kaftan
B
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.

Bigley answered 1/12, 2014 at 8:27 Comment(3)
This gets rid of the warning, but the alerts stack up on top of each other. Is there something I'm missingMelmon
This is expected behaviour with using UIAlertController and above solution. With UIAlertView iOS was managing showing/stacking alerts(hide existing alert when new alert is being presented then show previous alert when new one is dismissed). If you don't want to see previous alert when new one is presented you will have to dismiss previous one.Bigley
I was thinking of solution to this. I am not sure if it will work, please try and let us know. You can create your custom UIAlertController class. Implement viewWillAppear & viewWillDisappear(not sure if this will be called for previous alert when new alert is being presented) and invert view.alpha.Bigley
I
4

I fully understand the issue here and came up with the following solution via a category of UIAlertController. It's designed so that if an alert is already being presented, it delays showing of the next alert until it receives a notification that the first has been dismissed.

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

This code even handles the case where a previous alert was presented via the deprecated UIAlertView, i.e. it waits on it to finish too.

To test this out all you need to do is call show twice in a row with two different alert controllers and you will see the second one waits until the first has been dismissed before being presented.

Illegitimacy answered 4/2, 2016 at 21:2 Comment(9)
[super viewDidDisappear:] in your code will not do what you probably think. It won't call -[UIAlertControl viewDidDisappear:] but that one of the direct superclass, -[UIViewController viewDidDisappear:]. As you can't know what is in the implementation of -[UIAlertController viewDidDisappear:] that you are omitting, this is not "dangerous", this is a bug. There is a hacky technic to access the original implementation of a method from the override in the category (search for method swizzling), but it is really overkill - go for the subclass approach.Funke
Thanks the docs are ambiguous about that and even thought I had tested it but must have been mistaken. I updated the code to swizzle the method and tested via a subclass and it is working. I'm going to stick with the category because I'm confident I'll eventually figure out how to achieve this without the swizzle or retaining the window.Illegitimacy
Looks nice based on the golden words method swizzling and your SO scores... but did anyone successfully use this yet? Is it safe? Can I leave my kids in front of it while I go do something else?Wroth
Just another method I was thinking... subclass UIViewController, add a -(void)showUialertController:(UIAlertController*)alert; or similar, which underneath the hood carries out the queuing. Not sure if we'd have to care about UIAlertViews then.Wroth
Sometimes you want to show alerts from classes other than view controllers though.Illegitimacy
@Illegitimacy In NSNotificationCenter block alertView() must be replaced with [self mh_show] to handle the case for more than 2 concurrent windows (windowLevel must be checked again before presentation). Otherwise postponed alert controllers are presented simultaneously after receiving UIWindowDidBecomeHiddenNotification.Barouche
@RomanB. Thanks how can I try this out with 2 windows? Are you on OS X?Illegitimacy
@Illegitimacy I have tested that on iOS. Simply create 3 alert controllers and call mh_show. If you do that you will see first alert, than two overlapping alerts with darken background. Will my fix they will all show up one after another.Barouche
Great solution! I have no idea why Apple makes such a leap backwards in usability of the SDK and forces us to do such kind of hacks... Anyway, trying the code above I got an error from the compilator: "/Users/milen/Documents/Projects/KanbanizeMobile/KanbanizeMobile/Libs/UIAlertController+MH/UIAlertController+MH.m:18:9: Declaration of 'Method' must be imported from module 'ObjectiveC.runtime' before it is required" This is easily fixed by importing <objc/runtime.h>Guitarist
G
2

This solution is working for me. I have an AlertManager that is handling a queue of alerts that present one after another. To know when to present another alert, I am extending UIAlertController and overriding its viewDidDisappear function.

This solution must be used after viewDidAppear. If not, the alert won't be presented. The chain would be broken and no further alerts would be presented. Another option would be to try a hanging alert later or discard it, which would free up the queue for future alerts.

/// 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:)
Giess answered 15/9, 2016 at 7:27 Comment(0)
E
1

I wasn't happy with any of the solutions here as they required too much manual work or required swizzling which I'm not comfortable with in a production App. I created a new class (GitHub) which takes elements from other answers here.

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

Example usage

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];
Enunciation answered 4/2, 2017 at 0:34 Comment(2)
Nice solution, but actions do not work for some reason for me.Conciliar
If you haven't already, try the github version. I made a number of fixes and improvements.Enunciation
J
0

This can be solved by using a check flag in UIAlertcontroller's action handler.

- (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];
}
Jacquijacquie answered 5/3, 2015 at 21:49 Comment(0)
K
0

I have created a Github project MAAlertPresenter with a demo to handle this issue. You can use it to present UIAlertController one by one with just few lines of changes.

Kaftan answered 10/8, 2016 at 13:50 Comment(0)
S
0

It seems to be old question but still posting as It might be useful for someone looking for this though Apple doesn't recommend multiple alerts stacking thats why they deprecated this UIAlertView from to UIAlertController implementation.

I have created a AQAlertAction subclass for UIAlertAction. You can use it for staggering Alerts, the usage is same as you are using UIAlertAction. All you need to do is import AQMutiAlertFramework in your project or you can include class also (Please refer Sample project for that). Internally It uses binary semaphore for staggering the Alerts until user handle action associated with current alert displayed. Let me know if it works for you.

Schramm answered 15/9, 2016 at 4:11 Comment(2)
This could be flagged as spam unless you add some explanation to your answer instead of just asking people to use your code.Underclothes
I think I already explained it. All It does it keep adding alerts in a binary semaphore queue until user handle the action, which result in dispatching signal to semaphore. Please refer to code in case of confusions. Its very small implementation.Schramm
L
0

I also faced the same issue after switching from UIAlertView to UIAlertController. I don't like Apple policy because "Message Boxes" have always been stackable in almost every SO from the BIG BANG. I agree that having concurrent alerts is not a great user experience and sometimes it's the result of a bad design but sometimes (ex UILocalNotification or stuff like that) they just can happen and it's scared that I can loose an important blocking Alert just because my app has just received a notification.

That said, this is my 2cents solution, a recursive function which tries to present the alertcontroller on the sender if the sender has no presentedViewController, otherwise it tries to present the alertcontroller on the presentedViewController and so on... It does not work if you fire more AlertController exactly at the same time because you can't present a viewcontroller from a controller which is being presented but it should work in any other reasonable workflow.

+ (void)presentAlert:(UIAlertController *)alert withSender:(id)sender
{
    if ([sender presentedViewController])
    {
        [self presentAlert:alert withSender: [sender presentedViewController]];
    }
    else
    {
        [sender presentViewController:alert animated:YES completion:nil];
    }
}
Laverty answered 26/9, 2017 at 20:54 Comment(0)
S
0

If all you need is simple information alerts that are simply read and dismissed, then this is what I just came up with (it's not exactly fancy, high-level code and there is 'coupling' involved, but, hey... it's short/simple and may be useful in some cases):

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

}
Seguidilla answered 31/3, 2019 at 6:5 Comment(0)
A
-1

I solved this problem with this line of code :

alert.modalTransitionStyle=UIModalPresentationOverCurrentContext;
Atonality answered 22/1, 2015 at 15:41 Comment(1)
Incompatible types within your assignment. This is the corrected Swift style: alert.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContextStays

© 2022 - 2024 — McMap. All rights reserved.