How to Use Multiple Alerts in a Single SwiftUI View

Ever tried adding multiple .alert() modifiers to a SwiftUI view, only to watch in frustration as only one actually works? You're not alone. SwiftUI's alert system has a quirk that catches developers off guard: chaining alert modifiers simply doesn't work the way you'd expect. But there's good news—with the right approach, you can handle as many alerts as you need from a single view, and the solution is actually more elegant than you might think.
Method 1: Using Optional Identifiable Items (Recommended)
This is the most elegant approach using SwiftUI's built-in alert modifier with identifiable items.
struct ContentView: View {
enum ActiveAlert: Identifiable {
case delete, save, error
var id: Int {
hashValue
}
}
@State private var activeAlert: ActiveAlert?
var body: some View {
VStack(spacing: 20) {
Button("Show Delete Alert") {
activeAlert = .delete
}
Button("Show Save Alert") {
activeAlert = .save
}
Button("Show Error Alert") {
activeAlert = .error
}
}
.alert(item: $activeAlert) { alertType in
switch alertType {
case .delete:
return Alert(
title: Text("Delete"),
message: Text("Are you sure you want to delete?"),
primaryButton: .destructive(Text("Delete")) {
// Handle delete
},
secondaryButton: .cancel()
)
case .save:
return Alert(
title: Text("Save"),
message: Text("Do you want to save changes?"),
primaryButton: .default(Text("Save")) {
// Handle save
},
secondaryButton: .cancel()
)
case .error:
return Alert(
title: Text("Error"),
message: Text("Something went wrong"),
dismissButton: .default(Text("OK"))
)
}
}
}
}
Why this works: SwiftUI's .alert(item:) modifier watches for changes to an optional identifiable value. When the value changes from nil to a specific case, the corresponding alert appears. This uses a single alert modifier that dynamically displays different alerts based on the enum value.
Method 2: Using a Custom Alert Manager
For complex scenarios with many alerts across multiple views, create a dedicated manager to handle alert state.
class AlertManager: ObservableObject {
enum AlertType: Identifiable {
case delete(item: String)
case save
case error(message: String)
case success
var id: String {
switch self {
case .delete: return "delete"
case .save: return "save"
case .error: return "error"
case .success: return "success"
}
}
}
@Published var currentAlert: AlertType?
func showAlert(_ type: AlertType) {
currentAlert = type
}
}
struct ContentView: View {
@StateObject private var alertManager = AlertManager()
var body: some View {
VStack(spacing: 20) {
Button("Show Delete Alert") {
alertManager.showAlert(.delete(item: "Photo"))
}
Button("Show Save Alert") {
alertManager.showAlert(.save)
}
Button("Show Error Alert") {
alertManager.showAlert(.error(message: "Network error"))
}
}
.alert(item: $alertManager.currentAlert) { alertType in
switch alertType {
case .delete(let item):
return Alert(
title: Text("Delete \(item)"),
message: Text("This action cannot be undone"),
primaryButton: .destructive(Text("Delete")) {
// Handle delete
},
secondaryButton: .cancel()
)
case .save:
return Alert(
title: Text("Save Changes"),
message: Text("Save your work?"),
primaryButton: .default(Text("Save")) {
// Handle save
},
secondaryButton: .cancel()
)
case .error(let message):
return Alert(
title: Text("Error"),
message: Text(message),
dismissButton: .default(Text("OK"))
)
case .success:
return Alert(
title: Text("Success"),
message: Text("Operation completed"),
dismissButton: .default(Text("OK"))
)
}
}
}
}
Why this works: The AlertManager encapsulates all alert logic in a separate class, making it reusable across your app. You can inject this manager into multiple views via the environment, allowing centralized alert handling throughout your application.




