How to Show SwiftUI Alerts with Buttons, Textfields and Error Messages

We will explore how to show alerts using SwiftUI, taking into account the changes introduced in iOS 15 while also providing support for iOS 13 and 14. SwiftUI’s alert() modifier offers a straightforward way to present alerts, and we’ll delve into both the iOS 15 approach, which leverages standard SwiftUI buttons, as well as the dedicated Alert struct for earlier iOS versions.

I am going to use a Shopping app as an example that I started developing for other tutorials about SwiftUI presentations like sheets and popovers.

Download the project files here: https://github.com/gahntpo/Sho…

What is an alert in Swift and when should I use it?

An alert in Swift, particularly in SwiftUI, is a built-in user interface component that presents a way to inform users about important updates, errors, or decision points during the application’s runtime. It typically displays a short message or notification along with one or more buttons for users to acknowledge the information or make a choice. You should use alerts in situations where you need to capture the user’s attention immediately or require an immediate response from them. 

For instance, alerts are commonly used to confirm user actions, warn about potential consequences of a particular action, or notify about the completion or failure of a task. Remember, while alerts are handy tools, they should be used judiciously to avoid disrupting the user experience.

In the following example, my shopping app example is not able to send the product review from the user. I want to show an error message and let the user know the review cannot be added.

SwiftUI example for showing an alert when an error occurs
In this shopping app example, you can leave a review for each product. However, when you try to submit the review, you will see an error message inside an alert view.

Alert for iOS 13 and 14

SwiftUI introduced an alert modifier that takes in an Alert type. In the following, you can see a basic example:

struct SubmitReviewButtonView: View
    @State private var showAlert = false
    var body: some View {
        Button(action: {
            showAlert = true
        }) {
            Text("Submit")
        }
        .buttonStyle(.borderedProminent)
        .alert(isPresented: $showAlert) {
            Alert(title: Text("You are not connected to the internet."),
                  message: Text("Please, check settings."))
        }
    }
}

This will generate an alert with the title `You are not connected to the internet` and a message ´Please, check settings´. SwiftUI adds automatically an `OK` button, which when pressed dismiss the alert. 

You can change the `OK` button by using the dismissButton argument:

.alert(isPresented: $showAlert) {
    Alert(title: Text("You are not connected to the internet."),
         message: Text("Please, check settings."),
         dismissButton: .cancel(Text("Done"), action: {
               // do something when dismissed
           }))
}

You can also show multiple buttons by specifying the primary and secondary buttons. The following code will replicate the view from the above image:

.alert(isPresented: $showAlert) {
    Alert(
        title: Text("You are not connected to the internet."),
        message: Text("Please, check settings."),
        primaryButton: .cancel(
            Text("Cancel"),
            action: { }
        ),
        secondaryButton: .destructive(
            Text("Delete Review"),
            action: { }
        )
    )
}

Alert buttons were more difficult to use, since they are not simple SwiftUI views. They are also not very flexible. For example, you cannot add a textfield inside the alert. Luckily, things get better with iOS 15 and updated alerts.

New Alert with iOS 15 and macOS 12

Alerts in iOS 15 and macOS 12 changed significantly. As you will see in the coming example, they are much easier to use. Let´s rewrite the above example with the submit button. The new alert takes in a list of views, for which you will most likely use buttons. The following is the updated alert version:

.alert("You are not connected to the internet.",
       isPresented: $showAlert) {
       Button("Delete Review", role: .destructive) {
             // TODO
       }
} message: {
       Text("Please, check settings.")
}

I only added the `Delete Review` button with a destructive role, which changes its foreground color to red. The `Cancel` button is added automatically. 

Free SwiftUI Layout Book

Get Hands-on with SwiftUI Layout.

Master SwiftUI layouts with real-world examples and step-by-step guide. Building Complex and Responsive Interfaces.

How to add a textfield inside a SwiftUI Alert?

Because the actions closure expects views, you can declare any view like a button or textfield. This was not possible with the old alert. Simply add a TextField inside the alert modifier:

struct ContentView: View {

    @State private var showAlert = false
    @State private var text: String = ""

    let alertTitle: String = "We need to verify you again."

    var body: some View {
        Button("Get my tickets: \(text)") {
            showAlert = true
        }
        .alert(
            Text(alertTitle),
            isPresented: $showAlert
        ) {
            Button("Cancel", role: .cancel) {
                // Handle the acknowledgement.
            }
            Button("OK") {
                // Handle the acknowledgement.
            }

            TextField("enter", text: $text)
                .textContentType(.creditCardNumber)
        } message: {
           Text("Please enter you pin.")
        }
    }
}
SwiftUI alert with a textfield

Error Handling with Swift Alerts

Every application has to deal with errors, but error handling is not the most straightforward implementation. How do you tell the user that something went wrong? – alerts with error messages are a great way of handling errors. SwiftUI alert even has a build in implementation for errors. 

First, you have to declare an error type. In the following example, I used to error case. You would typically have many more for URLSession errors, decoding errors and more.

enum SubmitReviewError: Error, LocalizedError {
    case notBoughtProduct
    case noNetworkConnection

    var failureReason: String? {
        switch self {
            case .notBoughtProduct:
               return "Did not buy prouct."
            case .noNetworkConnection:
               return "No Internet."
        }
    }

    var recoverySuggestion: String? {
        switch self {
            case .notBoughtProduct:
               return "Maybe try next time."
            case .noNetworkConnection:
               return "Please, check settings."
        }
    }

    var errorDescription: String? {
        switch self {
            case .notBoughtProduct:
               return "Maybe try next time."
            case .noNetworkConnection:
               return "You are not connected to the internet."
        }
    }
}

Errors are enums. You need to conform to the `Error` protocol. I also added conformance to `LocalizedError` because this is a requirement for the alert modifier. 

Now, I can use it for my submit button like so:

struct SubmitReviewButtonView: View {

    @State private var showAlert = false
    @State private var error: SubmitReviewError? = nil

    var body: some View {
        Button(action: {
            error = .noNetworkConnection
            showAlert = true
        }) {
            Text("Submit")
        }
        .buttonStyle(.borderedProminent)
        .alert(isPresented: $showAlert, error: error) { _ in
            Button("Delete Review", role: .destructive) {

            }
        } message: { error in
            Text(error.recoverySuggestion ?? "Try again later.")
        }
    }
}

When you press the `submit` button, the state property showAlert is toggled to open the alert. I am also setting the error to the no-network connection case. This would typically come from handling your network request. 

I then use the error for the alert view and show the error recovery suggestion. The title of the alert is populated by the error failure reason that I defined above.

How to dismiss an Alert View?

An alert in SwiftUI is dismissed when you press any of the buttons shown inside the alert view. It is taken care of for you, unlike sheets or popovers where you have to call dismiss separately.

In the shopping app example from above, I added an alert with a `Delete Review` button.  When you press this button, I want to navigate backward inside the NavigationStack. This means moving back from the review view to the product detail view:

programmatically navigating backwards inside a SwiftUI NavigationStack
Programmatically navigating backward inside a SwiftUI NavigationStack

In order to programmatically navigate backward, I am going to use the environment property `dismiss`. When the user presses the `Delete Review` button, the alert is dismissed automatically. I am additionally calling `dismiss()` to push back in the stack.

struct SubmitReviewButtonView: View {

    @State private var showAlert = false
    @State private var error: SubmitReviewError? = nil

    @Environment(\.dismiss) var dismiss

    var body: some View {
        Button(action: {
            error = .noNetworkConnection
            showAlert = true
        }) {
            Text("Submit")
        }
        .buttonStyle(.borderedProminent)
        .alert(isPresented: $showAlert, error: error) { _ in
            Button("Delete Review", role: .destructive) {
                dismiss()
            }
        } message: { error in
            Text(error.recoverySuggestion ?? "Try again later.")
        }
    }
}

Conclusion

SwiftUI has significantly improved the way we handle alerts, bringing a high degree of flexibility and ease to the development process. Whether you’re working with iOS 15’s newer alerts or need to support earlier versions of iOS, understanding how to properly use alerts is crucial in crafting an intuitive and user-friendly application. The examples provided in this guide, using a shopping app, give you practical insights on how to implement alerts and handle errors effectively. Remember, the key is to use alerts sparingly and thoughtfully to avoid disrupting the user experience. As we’ve seen, SwiftUI alerts can even facilitate navigation in your app, as demonstrated by programmatically moving backwards in a navigation stack. With these techniques at your disposal, you are well-equipped to further enhance the user interaction in your SwiftUI applications. Happy coding!

Further Reading:

1 thought on “How to Show SwiftUI Alerts with Buttons, Textfields and Error Messages”

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!

Newsletter Form

Save 50% on all my course & books