Reading time: 2 min
This recipe shows how to show multiple Alert
s, ActionSheet
s 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),
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()
}