How to Handle Multiple Alerts in a Single View Using Enums

Following on from last month’s tutorial where I talked about presenting multiple sheets in a single View, we can also do the same with alerts. This is something I wish I knew when starting out with SwiftUI, as I first tried chaining multiple .alert() only to find that only one would work, followed by trying .background(EmptyView().alert(..) { .. }). If you haven’t read the linked post about handling multiple sheets, I highly recommend you check that out too.

While we could implement this without the below extension, we would have to make every definition of our ActiveAlert enum conform to Identifiable, which feels like an unnecessary piece of code that is repeated everywhere. This simple extension to View creates a boolean binding based on whether our ActiveAlert property has been set or is nil. We then leverage the existing alert(isPresented: ) view modifier that SwiftUI provides to present our alert, providing the Alert content and a callback that we can use inside our switch statement in our view.

extension View {
    public func alert<Value>(
        using value: Binding<Value?>,
        content: (Value) -> Alert
    ) -> some View {
        let binding = Binding<Bool>(
            get: { value.wrappedValue != nil },
            set: { _ in value.wrappedValue = nil }
        )
        return alert(isPresented: binding) {
            content(value.wrappedValue!)
        }
    }
}

Once that’s in place, it’s really easy to use across all of our views. Below is an example, where we define our ActiveAlert enum with the different kinds of alerts that we may present in this view, followed by some @State that determines whether there is an active alert or not. We then use our custom view modifier alert(using: ) to present our desired alert. You can copy this into your own app and see it in action!

struct ContentView: View {
    
    enum ActiveAlert { case saved, delete, error(_ message: String) }
    
    @State private var activeAlert: ActiveAlert?
    
    var body: some View {
        VStack {
            Button("Show Saved Alert") {
                activeAlert = .saved
            }.padding()

            Button("Show Delete Alert") {
                activeAlert = .delete
            }.padding()

            Button("Show Error Alert 1") {
                activeAlert = .error("Error 1")
            }.padding()

            Button("Show Error Alert 2") {
                activeAlert = .error("Error 2")
            }.padding()
        }
        .alert(using: $activeAlert) { alert in
            switch alert {
                case .saved:
                    return Alert(title: Text("Saved"), message: Text("Save is complete."))
                case .delete:
                    return Alert(
                        title: Text("Deleting"), 
                        message: Text("Are you sure you want to delete?"), 
                        primaryButton: .default(Text("No")), 
                        secondaryButton: .destructive(Text("Yes"))
                    )
                case .error(let message):
                    return Alert(title: Text("Uh Oh"), message: Text(message))
            }
        }
    }
}

Conclusion#

If this tutorial helped you in any way, I’d really appreciate a share on Twitter. You can reach out to me via Twitter if you have any questions, or to show me what you’ve been up to!