Hallo rivieren en oceanen, Leo hier. Today we will talk about how to present errors to your users in SwiftUI.
Error handling is important in SwiftUI because it allows you to gracefully handle unexpected conditions or errors that may occur in your code, such as network failures, invalid input, or missing data. By handling errors, you can provide meaningful feedback to the user, prevent data corruption or loss, and ensure that your app continues to function as expected. This can improve the overall stability and user experience of your app.
Error handling is something really important for mobile users. You need to provide clear and concise feedback so the user knows what to expect from your app. The more natural your app feels, the better.
Lately, I’m experimenting a lot with SwiftUI, and things are getting clearer now to me. How to make the view respond to state changes and navigate through views and even animations are getting easier to reason on. But one thing that was intriguing me was the fact that I still didn’t have to deal with error handling directly from SwiftUI. This week changed that.
I think is amazing what you can do with SwiftUI for example using the Timeline View and Canvas View to draw a soundwave. Or even create a background for a button that is animated. SwiftUI is a really interesting toolbox and I want to continue exploring it in the next months.
No more talking, let’s code! But first…
Painting of the Week
In 2008, a painting by Jean-Michel Basquiat was sold at auction for $13.5 million. This painting, which depicts a boxer in the foreground of a graffiti-filled backdrop, was part of the private collection of Lars Ulrich, the drummer for the heavy metal band Metallica. The decision to sell this painting, along with Basquiat’s other masterpiece Profit I, was made to raise funds to build a house for Ulrich’s family.
The painting is a representative example of Basquiat’s bold graffiti style, which he used to create a heroic art piece that also thoroughly examines the character’s figure and stance. This is evidenced by the skeleton-like smile, as well as the disfigured musculature in the arms and gloves of the boxer. The use of this style enhances the overall impact of the painting and conveys the depth of the character being depicted.
I chose this painting because this aesthetic remembers me of the missingno from pokemon. That was a bug in the game that you could use to multiply your items, and as we are talking about errors I found this very interesting.
Special Thanks To the Supporters
Thanks, Irina and MFanni, for supporting the blog with 10 coffees! You make this blog possible, all kudos to you!
I hope all the best to you and looking forward to seeing your code live soon. 😀
Problem – How to show errors in SwiftUI?
I want to show that an error ocurred to the user. Can be a popup, an alert, any visual hint that something was wrong.
This is something that usually happens often with us. I think most companies have a standard way to show errors. It can be a custom toast view, a popup, or even the default alert prompt from iOS. All of us, iOS developers have to deal with this kind of situation and we are not alone since anyone that works with software development knows that errors happen.
But why that is so important?
Why show errors to users in SwiftUI?
I can think of at least five arguments for you to show errors to your users.
- Provide clear and concise feedback: Error handling in SwiftUI helps to communicate the reason for an error to the user in a clear and concise manner, reducing confusion and frustration.
- Maintain app stability: Error handling can prevent crashes or other unexpected behavior in your app, ensuring a stable and reliable user experience.
- Prevent data loss: By handling errors gracefully, you can prevent data corruption or loss, ensuring that the user’s information is safe and secure.
- Improve user satisfaction: With well-designed error handling, users will be more likely to trust and continue using your app, leading to increased satisfaction and loyalty.
- Creating a smooth app experience: Error handling helps create a seamless user experience by preventing unexpected events and providing clear feedback.
In summary, SwiftUI error handling is important to users because it helps to create a smooth and reliable app experience, improves user trust and satisfaction, and protects valuable user data.
Let’s begin with the code examples of how you can handle errors in SwiftUI.
SwiftUI Error Handling Step-by-Step Tutorial
First, let’s set up something that will always fail. I’ll try to make it as simple as possible so it will be just a function that we can pass if it returns ok or not.
Start a new SwiftUI project and add the following ViewModel to it:
class UsersViewModel: ObservableObject { @Published var listUsers = [String]() @Published var userError: UserError? = nil @MainActor func loadUsers(withError: Bool) async { if withError { userError = UserError.failedLoading } else { userError = nil listUsers = ["ninha", "Mike", "Pepijn"] } } } enum UserError: Error { case failedLoading var description: String { switch self { case .failedLoading: return "Sorry, we couldn't retrieve users. \n Try again later. ☹️" } } }
Now we can control in the view what we will be receiving from the API. Nothing fancy here.
Now let’s create the three variations of SwiftUI error popups or views that you can use!
Substitute the Whole View for an Error View
This technique can be used with VStacks or just plain if statements. The idea here is to show or not a view that will overlay the entire screen showing the error and how to handle that.
Check below:
struct ContentView: View { @ObservedObject var usersViewModel = UsersViewModel() var body: some View { ZStack { List(usersViewModel.listUsers, id: \.self) { user in Text(user) } if let error = usersViewModel.userError { // << error handling here ErrorView(errorTitle: error.description, usersViewModel: usersViewModel) } } .task { try? await Task.sleep(for: .seconds(2)) // timer to fake the network request await usersViewModel.loadUsers(withError: true) // calling the fake function with error } } } struct ErrorView: View { let errorTitle: String @ObservedObject var usersViewModel: UsersViewModel var body: some View { RoundedRectangle(cornerRadius: 20) .foregroundColor(.red) .overlay { VStack { Text(errorTitle) .font(.largeTitle) .foregroundColor(.white) Button("Reload Users") { Task { await usersViewModel.loadUsers(withError: false) } } .buttonStyle(.borderedProminent) } } } }
The final result of this code should be:
In the ContentView we are using a timer to fake a calling of an API. Then we call our ViewModel that we have full control of the response.
The interesting part of this technique is that is very simple to just change the whole with the error view. Another variation of this technique is instead of using just two view states you can create an Enum an control what view will be showed based on the case of that enum.
Either way, this is a very common practice to show errors, or state changes in your apps.
Using Alert Modifier to Show Errors to the User
Alert modifier has changed throughout the SwiftUI versions. This article will not discuss that. We will use the iOS 15 and above version of it today.
To show the error when the api returns an error, we need to use the onChange modifier to listen to changes of the @Published error property in the ViewModel. Only then we can manage the state of the view and then you can show the error alert.
Disclaimer: a better way to deal with this would the viewModel async function throws the error and the view handle the error and change the state in the spot. I’m trying to not change the ViewModel this way you can see various techniques with the same design, the idea is just understand how the alert can be shown.
Check the code below:
struct ContentView: View { @ObservedObject var usersViewModel = UsersViewModel() @State var showAlert = false var body: some View { ZStack { List(usersViewModel.listUsers, id: \.self) { user in Text(user) } } .task { try? await Task.sleep(for: .seconds(2)) await usersViewModel.loadUsers(withError: true) } .onChange(of: usersViewModel.userError, perform: { newValue in showAlert = newValue != nil }) .alert(usersViewModel.userError?.description ?? "", isPresented: $showAlert) { ErrorView(usersViewModel: usersViewModel) } } } struct ErrorView: View { @ObservedObject var usersViewModel: UsersViewModel var body: some View { RoundedRectangle(cornerRadius: 20) .foregroundColor(.red) .overlay { VStack { Button("Reload Users") { Task { await usersViewModel.loadUsers(withError: false) } } .buttonStyle(.borderedProminent) } } } }
Resulting in this view:
Observe that the same view that we use for the first solution we are passing to the Alert modifier now, but it only considers the button view. This happens because the Alert modifier is expecting buttons in the Actions closure, so it parses any button in the view hierarchy that you send to it.
Another thing to be aware of is that SwiftUI Alert modifier has a version that receives an error. The problem with that is that we would have to make our Error conform to the LocalizedError protocol, and also make some changes in the view model to make it pretty.
Again, I don’t want to change the view model layer because we want to show three different ways to present errors with the same business layer design.
Using Toast or Popup Animated View to Show Errors in SwiftUI
This solution doesn’t involve using sheets to present errors, although you could. Here we will build the same view that we are using in all other errors but now we will create a small card that will be our toast view that will popup from the top to show errors for the user.
Check the code popup error code below:
struct ContentView: View { @ObservedObject var usersViewModel = UsersViewModel() @State var showError = false var body: some View { VStack { List(usersViewModel.listUsers, id: \.self) { user in Text(user) }.overlay { if showError { VStack { ErrorView(errorTitle: usersViewModel.userError?.description ?? "", usersViewModel: usersViewModel) Spacer() }.transition(.move(edge: .top)) } }.transition(.move(edge: .top)) } .animation(.default, value: usersViewModel.userError) .task { try? await Task.sleep(for: .seconds(1)) await usersViewModel.loadUsers(withError: true) } .onChange(of: usersViewModel.userError) { newValue in showError = newValue != nil } } } struct ErrorView: View { let errorTitle: String @ObservedObject var usersViewModel: UsersViewModel var body: some View { RoundedRectangle(cornerRadius: 20) .foregroundColor(.red) .frame(height: 200) .padding() .overlay { VStack { Text(errorTitle) .font(.largeTitle) .foregroundColor(.white) Button("Reload Users") { Task { await usersViewModel.loadUsers(withError: false) } } .buttonStyle(.borderedProminent) } } } }
With the final result:
If you look closely at this solution we are just using an overlay for the view. This is good because the whole point of the toast error is just be a visual hint for the user that something was not ok. This way we don’t need to remove the whole view below and we can just show the above it.
There are some issues that I don’t really understand about transitions in SwiftUI yet. For example: why the emoji is not animated? Why do I need to use two transitions to make this animate in the insertion and the view removal? We will have to answer all of that in another article. All of the SwiftUI animations still look like magic to me.
And we are done!
Wrap up – Displaying Errors In SwiftUI
Today we talked about three ways to present errors to your users in SwiftUI.
First, we described how to change the whole view to one that shows an error to the user. This is handy when nothing in your UI works because of that error and doesn’t worth it half load your views.
Then we explored using the default Alert modifier to show errors to the users. Going this path, you chose the most vanilla way to show an error in iOS and I rarely see this in other apps that are not Apple. Even in Apple Apps, I don’t record seeing it.
And lastly, we built a custom popup with an animated toast view in SwiftUI with an overlay. Chose this option when you don’t necessary want to change the whole screen for your error screen, you just want to warn the user that something doesn’t work as expected.
Summary – How to Display Errors in SwiftUI?
In conclusion, error handling in SwiftUI is essential to creating a seamless user experience. By following the best practices and techniques outlined in this guide, you can effectively present errors to users in a way that is both informative and visually appealing.
Whether you’re a seasoned developer or just starting out, mastering error handling in SwiftUI will make a significant impact on the overall quality of your applications.
So go ahead, give it a try! You’ll be surprised at how easy it can be to create clear and effective error messages that enhance the user experience.
Fellow Apple Developers, that’s all. I hope you liked reading this article as much as I enjoyed writing it.
If you want to support this blog you can Buy Me a Coffee or say hello on Twitter. I’m available on LinkedIn or send me an e-mail through the contact page.
You can likewise sponsor this blog so I can get my blog free of ad networks.
Thanks for the reading and… That’s all folks.
Image credit: Featured Painting