> ## Content Index
> Fetch the complete content index at: https://williamboles.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Alert Queuing with Windows
- URL: https://williamboles.com/alert-queuing-with-windows/
- Published: 2019-11-06T14:25:57.000Z
- Updated: 2025-09-20T19:59:25.000Z
- Description: Being British, queuing is essential to me. I take every opportunity I can get to queue. However, UIAlertController doesn't care about queuing 😞. Here, we look at how to introduce UIAlertController to the joys of queuing and ensure that each alert gets its chance to shine.
- Author: William Boles
- Tags: UIKit, Alerts

Being [British](https://en.wikipedia.org/wiki/British%5Fpeople?ref=williamboles.com), queuing is essential to me. I take every opportunity I can get to queue: posting a package ✅, paying for my groceries ✅, fleeing a burning building ✅, etc. So every time I need to present a sequence of alerts, I come up against the uncomfortable truth that [UIAlertController](https://developer.apple.com/documentation/uikit/uialertcontroller?ref=williamboles.com) doesn't care as much about queuing as I do 😔.

![Photo of people standing in truly glorious queuing](https://storage.ghost.io/c/b9/b4/b9b42214-5933-4584-8e56-9ee8ca21d23b/content/images/2020/05/people_queuing.jpg)

This post is a look at how `UIAlertController` can be made a little more British through embracing a strong queuing etiquette.

## Let's get queuing 🇬🇧

A [queue](https://en.wikipedia.org/wiki/Queue%5F%28abstract%5Fdata%5Ftype%29?ref=williamboles.com) is a *first-in, first-out* data structure so that the oldest element is the first to be removed. Swift doesn't come with a built-in queue type, so let's build one:

```
struct Queue<Element> {
    private var elements = [Element]()

    // MARK: - Operations

    mutating func enqueue(_ element: Element) {
        elements.append(element)
    }

    mutating func dequeue() -> Element? {
        guard !elements.isEmpty else {
            return nil
        }

        return elements.removeFirst()
    }
}
```

The above class is a relatively trivial generic queue implementation that allows only two operations - enqueue and dequeue.

When queuing alerts, it's important to ensure that all alerts are queued on the same instance. To achieve this, we need a specialised singleton class that will control access to the queue and be the central component for all alert presentation logic:

```
class AlertPresenter {
    private var alertQueue = Queue<UIAlertController>()

    static let shared = AlertPresenter()

    // MARK: - Present

    func enqueueAlertForPresentation(_ alertController: UIAlertController) {
        alertQueue.enqueue(alertController)

        //TODO: Present
    }
}
```

In the above class, `AlertPresenter` holds a private queue specialised for `UIAlertController` elements and a method for enqueuing alerts. But as of yet, no way to present the queued alert. Before we can implement a presentation method, let's look at how alerts are normally presented:

```
let alertController = ...

present(alertController, animated: true, completion: nil)
```

`UIAlertController` (which is a subclass of `UIViewController`) is presented modally like any other view-controller by calling `present(_:animated:completion:)` on the presenting view-controller. A consequence of queuing alerts in `AlertPresenter` is that it breaks the usual alert presentation flow. As `AlertPresenter` isn't a subclass of `UIViewController`, we can't use it directly for presenting. Instead, we need to get a view-controller to present from. As well as requiring a view-controller to be injected into `AlertPresenter`, our indirect alert presentation also exacerbates an existing subtle issue with presenting alerts - simultaneous navigation events (presentation/dismissal) occurring at the same time, resulting in one of those events being cancelled and the following error being generated:

![Screenshot showing a navigation collision between two view-controllers preventing one of the events from occuring](https://storage.ghost.io/c/b9/b4/b9b42214-5933-4584-8e56-9ee8ca21d23b/content/images/2019/11/screenshot_of_navigation_collision.png)

> Without `AlertPresenter` being involved, the alert would be presented directly from the view-controller, allowing that view-controller to prevent other navigation events from occurring until after the alert is dismissed. However, by queuing the alert, the view-controller has no way of knowing when it will be shown, so no way of knowing when it should or shouldn't prevent other navigation events.

While it's possible for `AlertPresenter` to query the topmost view-controller and determine if it is in the process of presenting or being dismissed, doing so requires logic that gets messy quickly 🤢. Instead of having to accommodate events happening on the navigation stack that `AlertPresenter` doesn't control, we can raise `AlertPresenter` above all navigation concerns by using a dedicated [UIWindow](https://developer.apple.com/documentation/uikit/uiwindow?ref=williamboles.com) instance (with a dedicated view-controller) to present the queued alerts from. As the navigation stack in one window is independent of any other window's navigation stack, an alert can be presented on its window at the same time as a view controller is being pushed on its window without navigation collisions 🥳.

Before delving into how to use a dedicated window to present alerts, let's get to know windows better.

> If you are comfortable with how `UIWindow` works, feel free to [skip ahead](#usingwindows).

## Getting to know windows 💭

`UIWindow` is a subclass of `UIView` that acts as the container for an app's visible content - it is the top of the view hierarchy. All views that are displayed to the user need to be added to a window. An app can have multiple windows, but only windows that are *visible* can have their content displayed to the user - by default, windows are not visible. Multiple windows can be visible at once. Each window's UI stack is independent of other windows UI stacks. Where a window is displayed in relation to other visible windows is controlled by setting that window's `windowLevel` property. The higher the `windowLevel`, the *nearer* to the user that window is. `UIWindow` has three default levels:

1. `.normal`
2. `.statusBar`
3. `.alert`

With `.alert` \> `.statusBar` \> `.normal`. If a more fine-grain level of control is needed, it's possible to use a custom level:

```
window.windowLevel = .normal + 25
```

> As of iOS 13: `.alert` has a raw value of 2000, `.statusBar` has a raw value of 1000 and `.normal` has a raw value of 0.

Where two or more windows have the same level, their ordering is determined by the order they were made visible in - the last window made visible is *nearest* one to the user.

It's unusual to add subviews to a window directly; instead, each window should have a root view-controller whose view is used as the window's initial subview.

As well as displaying content, `UIWindow` is also responsible for forwarding any [events](https://developer.apple.com/documentation/uikit/uievent/eventtype?ref=williamboles.com) (touch, motion, remote-control or press) to interested parties in its [responder chain](https://swiftrocks.com/understanding-the-ios-responder-chain.html?ref=williamboles.com). While all touch events are forwarded to the responder chain of the window that the event occurred on, events that are outside of the app's UI, such as motion events or keyboard entry, are forwarded to the *key* window. Only one window can be *key* at any one given time (which window is *key* can change throughout the lifetime of the app).

An iOS app needs at least one window; a reference to this window can be found in the app delegate:

```
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    //Omitted methods
}
```

If you're using storyboards to lay out your interface, then most of the work of setting up this window is happening under the hood. When the app is launched, a `UIWindow` instance is created that fills the screen. This window is then assigned to the `window` property (declared in the `UIApplicationDelegate` protocol), configured with the view-controller declared as the *storyboard entry point* from the project's main storyboard as its `rootViewController`, and finally, the window is made **key and visible**.

If you are not using storyboards, you can better see this setup:

```
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    // MARK - AppLifecycle

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow()
        window?.rootViewController = YourViewController()
        window?.makeKeyAndVisible()

        return true
    }
}
```

## Using windows

As this new window will only display alerts, let's subclass `UIWindow` for this single purpose:

```
class AlertWindow: UIWindow {
    // MARK: - Init

    init(withAlertController alertController: UIAlertController) {
        super.init(frame: UIScreen.main.bounds)

        rootViewController = alertController

        windowLevel = .alert
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("Unavailable")
    }

    // MARK: - Present

    func present() {
        makeKeyAndVisible()
    }
}
```

In the above `AlertWindow` class, an alert is passed into the init'er and set to the window's `rootViewController`, the window is then configured to have a `.alert` window level - this will put it above the app's main window (which by default has a `.normal` window level).

If you create an instance of `AlertWindow` and make it visible, you will notice that it's not being presented with an animation - it just appears, which feels weird. A window's root view-controller can not be animated on-screen, so to keep the alert's animation, we need that alert to be presented from an intermediate view-controller, which can be the window's root view-controller:

```
class HoldingViewController: UIViewController {
    private let alertController: UIAlertController

    // MARK: - Init

    init(withAlertController alertController: UIAlertController) {
        self.alertController = alertController
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - ViewLifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .clear
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        present(alertController, animated: true, completion: nil)
    }
}
```

The above `HoldingViewController` class only has one responsibility - presenting an alert once `viewDidAppear(_:)` has been called.

> Trying to present an alert earlier will cause an animation error due to the `HoldingViewController` instance not having finished its own "animation" on screen. While (at the time of writing - iOS 13) this doesn't seem to affect the actual presentation of the alert, waiting for `HoldingViewController` to be presented before attempting another presentation ensures that the error isn't produced.

Let's use `HoldingViewController`:

```
class AlertWindow: UIWindow {

    // MARK: - Init

    init(withAlertController alertController: UIAlertController) {
        super.init(frame: UIScreen.main.bounds)

        rootViewController = HoldingViewController(withAlertController: alertController)

        windowLevel = .alert
    }

    //Omitted methods
}
```

In the above class, instead of the `UIAlertController` instance being set directly as the `rootViewController` it is used to create an `HoldingViewController` instance, which is set as the window's `rootViewController`.

Each `AlertWindow` instance is intended to be single-use - to present one alert. This single-use nature allows for its mere presence to be the determining factor in whether a queued alert should be presented or not:

```
class AlertPresenter {
    //Omitted properties

    private var alertWindow: AlertWindow?

    // MARK: - Present

    func enqueueAlertForPresentation(_ alertController: UIAlertController) {
        alertQueue.enqueue(alertController)

        showNextAlertIfPresent()
    }

    private func showNextAlertIfPresent() {
        guard alertWindow == nil,
            let alertController = alertQueue.dequeue() else {
                return
        }

        let alertWindow = AlertWindow(withAlertController: alertController)
        alertWindow.present()

        self.alertWindow = alertWindow
    }
}
```

With the above changes, `alertWindow` holds a reference to the window that is being used to present the alert. Inside `showNextAlertIfPresent()` if `alertWindow` is `nil` and there is a queued alert, then a new `AlertWindow` instance is created, presented and assigned to `alertWindow`. Making the window *key and visible* sets off the chain of activity that results in the alert being animated on screen.

The example so far, while functional, is limited - it can present only one alert. `AlertPresenter` needs to be informed when an alert has been dismissed so it can move on to the next alert.

It turns out knowing when an alert has been dismissed is one of the trickier things to know about in an iOS app 😕. I had to work through a number of different solutions before I got somewhere I was happy:

1. My first attempt involved subclassing `UIAlertController` and overriding `viewDidDisappear(_:)` with a call to `AlertPresenter` \- everyone would then use this subclass instead of `UIAlertController` directly. However, this approach is explicitly warned against in the documentation for `UIAlertController`:

![Screenshot of UIAlertController documentation showing the warning](https://storage.ghost.io/c/b9/b4/b9b42214-5933-4584-8e56-9ee8ca21d23b/content/images/2019/10/screenshot_of_uialercontroller_documentation.png)

👎

1. My second attempt involved requiring each developer to explicitly call `AlertPresenter` from inside each `UIAlertAction` closure. However, this approach puts too much of a burden on each developer to remember to include those calls in each and every action. A missed call from any action closure (that was substantially triggered) would cause the queue to be *jammed* from that alert until the app was killed. This is too easy a requirement to forget when writing or reviewing 👎.
2. My third attempt involved using a view model to abstract the creation of `UIAlertController` instances to a [factory method](https://en.wikipedia.org/wiki/Factory%5Fmethod%5Fpattern?ref=williamboles.com) where a call to `AlertPresenter` could then be injected into each `UIAlertAction` closure (during the conversion from view model to `UIAlertController` instance). However, this approach would require a lot of custom code to be written, maintained and tested - using `UIAlertController` directly would avoid all effort 👎.

Tricky, tricky 🤔.

Instead of thinking about how to get `UIAlertController` to tell us about what was happening to it, I decided to start thinking about what impact `UIAlertController` had on its surroundings. `UIViewController` has a great suite of methods for when it will appear and disappear - these appearance events can tell us what has happened to the presented alert. When an alert is dismissed, it calls `func dismiss(animated:completion:)` on the view-controller it was presented from. We can hook into this behaviour to inform `AlertWindow` about the dismissal:

```
protocol HoldingDelegate: class {
    func viewController(_ viewController: HoldingViewController, didDismissAlert alertController: UIAlertController)
}

class HoldingViewController: UIViewController {
    //Omitted properties

    weak var delegate: HoldingDelegate?

    // Omitted methods

    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        //Called when a UIAlertController instance is dismissed
        super.dismiss(animated: flag) {
            completion?()

            self.delegate?.viewController(self, didDismissAlert: self.alertController)
        }
    }
}
```

#### 👍

> You may be thinking: "Why is `func dismiss(animated:completion:)` being called on a view-controller that isn't being dismissed instead of `viewDidAppear(_:)` as it is actually reappearing"?. The same thought occurred to me, and this mismatch between expectations vs reality left me a little uncomfortable 😒. However, in the end, I decided that being able to use `UIAlertController` without having to care about the queue outweighed my discomfort. If Apple changes this to better match the behaviour of other `UIViewController` instances, modifying this solution to handle both cases will be trivial.

When an alert has been dismissed, the presenting `AlertWindow` instance has served its purpose and can itself be dismissed:

```
class AlertWindow: UIWindow, HoldingDelegate {
      //Omitted properties

      // MARK: - Init

      init(withAlertController alertController: UIAlertController) {
          super.init(frame: UIScreen.main.bounds)

          let holdingViewController = HoldingViewController(withAlertController: alertController)
          holdingViewController.delegate = self

          rootViewController = holdingViewController

          windowLevel = .alert
      }

      //Omitted other methods

      // MARK: - HoldingDelegate

      func viewController(_ viewController: HoldingViewController, didDismissAlert alertController: UIAlertController) {
          resignKeyAndHide()
      }

      // MARK: - Resign

      private func resignKeyAndHide() {
          resignKey()
          isHidden = true
      }

}
```

All that is left to do is inform `AlertPresenter` that the alert has been dismissed, again the [delegation pattern](https://en.wikipedia.org/wiki/Delegation%5Fpattern?ref=williamboles.com) can be used here:

```
protocol AlertWindowDelegate: class {
    func alertWindow(_ alertWindow: AlertWindow, didDismissAlert alertController: UIAlertController)
}

class AlertWindow: UIWindow, HoldingDelegate {
    weak var delegate: AlertWindowDelegate?

    //Omitted properties and methods

    func viewController(_ viewController: HoldingViewController, didDismissAlert alertController: UIAlertController) {
        resignKeyAndHide()
        delegate?.alertWindow(self, didDismissAlert: alertController)
    }

    //Omitted methods
}
```

`AlertPresenter` then just needs to present the next alert (if present):

```
class AlertPresenter: AlertWindowDelegate {
    //Omitted properties and methods

    private func showNextAlertIfPresent() {
        guard alertWindow == nil,
            let alertController = alertQueue.dequeue() else {
                return
        }

        let alertWindow = AlertWindow(withAlertController: alertController)
        alertWindow.delegate = self

        alertWindow.present()

        self.alertWindow = alertWindow
    }

    // MARK: - AlertWindowDelegate

    func alertWindow(_ alertWindow: AlertWindow, didDismissAlert alertController: UIAlertController) {
        self.alertWindow = nil
        showNextAlertIfPresent()
    }
}
```

## All queued out 🍾

Congratulations on having made it to the end of a post about queuing. You've shown a level of patience that even many Brits would struggle to achieve.

To recap, in this post, we implemented a minimally intrusive alert queuing mechanism that allows us to continue using `UIAlertController` without having to ask the creators of those alerts to do anything more complicated than calling `AlertPresenter.shared.enqueueAlertForPresentation(_:)`.

To see the above code snippets together in a working example, head over to the [repository](https://github.com/wibosco/AlertQueue-Example?ref=williamboles.com) and clone the project.