Reading time: 2 min

This recipe shows how to show multiple Alerts, ActionSheets or custom dialogs, without tying them to multiple views.

The end result looks like this:


The usual way of using multiple views has its shortcomings, as it:

  • Forces you to have at least as many views as distinct modals you wish to show.
  • Makes your code cluttered and more difficult to read.
  • Sometimes it doesn't work, especially in a more complex view hierarchy - the alerts might get hidden, custom dialogs improperly sized, etc.

As you'll see, using a ViewModifier can make all those issues go away and leave you with a clean code. Let's get started!

This recipe makes use of our Custom View Dialog. If you only need Alerts and Action Sheets, feel free to omit the sun and progress parts of the recipe.

First, add an enum that describes dialog types:

enum DialogType {
  case alert(() -> Alert),
       sheet(() -> ActionSheet),
       progress(String, Progress)

Then, add this ViewModifier that displays various modals based on type. Also add a convenience extension that applies that modifier:

struct MultiDialog: ViewModifier {
  @Binding var isPresented: Bool
  @Binding var type: DialogType

  func body(content: Content) -> some View {
    if case let .alert(alert) = type {
      content.alert(isPresented: $isPresented, content: alert)
    } else if case let .sheet(sheet) = type {
      content.actionSheet(isPresented: $isPresented, content: sheet)
    } else if case .sun = type {
      content.genericDialog(isShowing: $isPresented) {
        VStack {
          Image(systemName: "sun.max")
            .frame(width: 80, height: 80)
          Text("Here's a nice sun for you!")
    } else if case let .progress(message, progress) = type {
      content.progressDialog(isShowing: $isPresented, message: message, progress: progress)

extension View {
  func multiDialog(isPresented: Binding<Bool>, type: Binding<DialogType>) -> some View {
    self.modifier(MultiDialog(isPresented: isPresented, type: type))

And that's it! Here's a sample usage, resulting in the view shown in the image above:

@State private var isShowing = false
@State private var dialogType = DialogType.sun

private func typeButton(_ title: String, type: DialogType) -> some View {
  Button(title) {
    dialogType = type
    isShowing = true

var body: some View {
  VStack(spacing: 20) {
    typeButton("Alert", type: .alert({
      Alert(title: Text("Alert title"),
            message: Text("Alert message"),
            primaryButton: .default(Text("Button")),
            secondaryButton: .cancel())
      typeButton("Action Sheet", type: .sheet({
        ActionSheet(title: Text("Title"),
                    message: Text("This is an action sheet"),
                    buttons: [
                      .default(Text("OK"), action: { }),
                      .destructive(Text("Delete"), action: { }),
      typeButton("Custom", type: .sun)
      typeButton("Progress", type: .progress("Progress message", Progress(totalUnitCount: 10)))
    .multiDialog(isPresented: $isShowing, type: $dialogType) // HERE

Next Post Previous Post