Reading time: 2 min

This recipe shows how to show an alert dialog from anywhere in the SwiftUI app. Normally, alert is tied to a specific view, and its content is handled by that view's state. This solution, however, makes it so that the alert is defined in just a single place and is always presented on top of current view.

The end result (which admittedly doesn't fully convey what's going on) looks like this:

preview

Recipe

Two things need to be put in place for this to work:

  1. Send a Notification from wherever in the app, with its payload object describing the alert to be shown.
  2. Receive that notification in your app file, where an alert has been attached to your ContentView.

First, add the name for the new notification:

public extension Notification.Name {
  static let showAlert = Notification.Name("showAlert")
}

Then, define the model for the alert dialog shown. This will be sent in the notification itself and then translated into an Alert view later on:

struct AlertData {
  let title: Text
  let message: Text?
  let dismissButton: Alert.Button?

  static let empty = AlertData(title: Text(""),
                               message: nil,
                               dismissButton: nil)
}

Next, open your app file. It's the one that contains a struct that inherits from App. It should roughly look like this:

@main
struct MyAwesomeApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

Now, extend it with:

  1. Few @State vars that represent the alert state.
  2. onReceive to handle incoming notifications to show the alert.
  3. alert to render the alert on top of ContentView.

Here's what it should look like in the end:

@main
struct MyAwesomeApp: App {
  @State private var showAlert = false
  @State private var alertData = AlertData.empty

  var body: some Scene {
    WindowGroup {
      ContentView()
        .onReceive(NotificationCenter.default.publisher(for: .showAlert)) { notif in
          if let data = notif.object as? AlertData {
            alertData = data
            showAlert = true
          }
        }
        .alert(isPresented: $showAlert) {
          Alert(title: alertData.title,
                message: alertData.message,
                dismissButton: alertData.dismissButton)
        }
    }
  }
}

And that's actually it! Now, just send the notification from anywhere in your app to make the magic happen:

Button("Show alert") {
  NotificationCenter.default.post(name: .showAlert,
                                  object: AlertData(title: Text("Content View Alert"),
                                                    message: Text("I'm shown on top of everything!"),
                                                    dismissButton: .default(Text("OK")) {
    print("Alert dismissed")
  }))
}

Next Post Previous Post