Using ContentUnavailableView for Error and Empty Views in SwiftUI

ยท

4 min read

Using ContentUnavailableView for Error and Empty Views in SwiftUI

Hi everyone, in today's article, I want to share something I learned a few days ago: the existence of ContentUnavailableView to quickly create error or "no data" screens.

What is the problem?

How many of you have created a custom view for displaying network issues when an API call returns an error? Or what about a list with no data? I assume most of us have created a view like that while working on Apple platforms.

Sometimes, this task is repetitive in every project. For example, for this demo that explains the MV pattern, I had to create my own custom views for empty lists and network errors:

NavigationView {
    Group {
        switch productStore.loadingState {
        case .loading, .notStarted:
            ProgressView()
                .frame(width: 100, height: 100)
                .task {
                    await productStore.fetchProducts()
                }
        case .error(let message):
            ProductErrorView(message: message) // <----
        case .empty:
            Text("No Products Found") // <----
        case .loaded(let products):
            List(products) { product in
                ProductCell(product)
            }
            .refreshable {
                await productStore.fetchProducts()
            }
        }
    }
    .navigationTitle("Products")
    // ...
}

However, in WWDC23, Apple introduced ContentUnavailableView, which is a built-in solution for displaying these types of screens.

Let's see how easy it is to use ContentUnavailableView in your project!

Using ContentUnavailableView with title, description and actions

Let's start by replacing the empty view, which is currently just a Text. We'll implement it with the simplest form of ContentUnavailableView using a label:

NavigationView {
    Group {
        switch productStore.loadingState {
        // ...
        case .error(let message):
            ProductErrorView(message: message) 
        case .empty:
            ContentUnavailableView {
                Text("No Products Found")
            }    
        // ... 
    }
    .navigationTitle("Products")
    // ...
}

Giving the same result as using Text directly.

Of course, we don't want to have the same result with more lines of code. But from here, you can add more components, for example, a description:

ContentUnavailableView {
    Text("No Products Found")
} description : {
    Text("More products will come soon")
}

and actions, such as a button to retry fetching information:

ContentUnavailableView {
    Text("No Products Found")
} description : {
    Text("More products will come soon")
} actions: {
    Button {
        Task {
            await productStore.fetchProducts()
        }
    } label: {
        Text("Retry")
            .font(.title)
    }
}

Not bad, but we can do more. ContentUnavailableView supports images too. Let's look at that setup.

Using ContentUnavailableView with images

To make the previous screen more appealing to the user, let's add an image that reinforces the idea of no data:

ContentUnavailableView {
    Label{
        Text("No Products Found")
    } icon: { 
        Image("question") // <----------
            .resizable()
            .frame(width: 100, height: 100)
    }
} description : {
    Text("More products will come soon")
} actions: {
    Button {
        Task {
            await productStore.fetchProducts()
        }
    } label: {
        Text("Retry")
            .font(.title)
    }
}

As you can see, I'm not a UX designer, but you get the idea. ContentUnavailableView allows to add images for screens displaying no content or error.

Speaking of error, let's update our custom error view. Currently, ProductErrorView is displaying an error message and an image when we have a network issue:

NavigationView {
    Group {
        switch productStore.loadingState {
        // ...
        case .error(let message):
            ProductErrorView(message: message) // <---------
        case .empty:
            ContentUnavailableView {
                ...
            }    
        // ... 
    }
    .navigationTitle("Products")
    // ...
}

For this case, we don't need an action button. ContentUnavailableView also offers a simpler setup with text and images. Let's replace the error view with this:

ContentUnavailableView(
    "There was a problem reaching the server. Please try again later.",
    image: "errorCloud"
)

Great! We basically got the same result, and also we can add a description providing even more information:

ContentUnavailableView(
    "There was a problem reaching the server. Please try again later.",
    image: "errorCloud",
    description: Text(message)
)

That description is a literal print from error.localizedDescription. It's just for demonstration purposes.

ContentUnavailableView built-in views

Lastly, Apple created some built-in solutions to create ContentUnavailableView even faster. For example, this one for no data after searching:

ContentUnavailableView.search

At the time of publishing this article, that's the only built-in view available. I hope Apple will add more in the future. However, I think it's also a good opportunity for the community to create their own built-in screens. If you create one, please let me know in the comments so I can share it on my social media ;).

Wrap Up

ContentUnavailableView is a great tool for situations where we need to display something other than the regular app data due to an error or because there's no data to show.

This approach is useful if you are prototyping or starting a new app and want to focus on the core features, rather than spending time on these use cases that can be addressed in a later iteration.

In the end, it's another resource for your development. My goal is to show you this and more tools and tips for Swift and SwiftUI! :)

By the way, ContentUnavailableView is only available for iOS 17.0+, macOS 14.0+, watchOS 10.0+ and vision 1.0+

Let me know what other tips and tricks you would like to learn in the comment section below.

Remember, my name is Pitt, and this is swiftandtips.com. Thanks for reading and have a great day! ๐Ÿ‘‹๐Ÿป

Social Media

References

https://developer.apple.com/documentation/swiftui/contentunavailableview

ย