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:

ezgif-3-31b97e7c1ad8

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),
       sun,
       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")
            .foregroundColor(.orange)
            .frame(width: 80, height: 80)
          Text("Here's a nice sun for you!")
        }.padding()
      }
    } 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) {
    Spacer()
    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: { }),
                      .cancel(Text("Cancel"))
                   ])
      }))
      typeButton("Custom", type: .sun)
      typeButton("Progress", type: .progress("Progress message", Progress(totalUnitCount: 10)))
      Spacer()
    }
    .multiDialog(isPresented: $isShowing, type: $dialogType) // HERE
    .padding()
}

Next Post Previous Post