⚠️ Information in this article is based on the Xcode 15 beta 1 release and may be subject to change.
In this year WWDC (WWDC23), Apple surprised developers with an unexpected improvement to UIKit. The introduction of UIContentUnavailableConfiguration
aimed to simplify the process of creating empty states for view controllers.
According to Apple, UIContentUnavailableConfiguration
is a composable description of an empty state and can be provided with placeholder content, such as an image or text.
Here’s an example of an empty state showcased in WWDC:
Now, let’s get into the details.
Note:
This article primarily focuses on the UIKit side of things. If you’re interested in learning how to do the same thing in SwiftUI, I recommend checking out this article by Antoine van der Lee.
Creating a UIContentUnavailableConfiguration
There are essentially 4 ways to create a UIContentUnavailableConfiguration
:
- Creating from scratch
- Using the predefined loading configuration
- Using the predefined search configuration
- Using
UIHostingConfiguration
1. Creating from Scratch
To start from scratch, we must first create an empty UIContentUnavailableConfiguration
.
var config = UIContentUnavailableConfiguration.empty()
After that, we will have to configure the UIContentUnavailableConfiguration
‘s placeholder content based on our needs:
config.image = UIImage(systemName: "applelogo")
config.text = "WWDC23 Demo"
config.secondaryText = "Code new worlds."
Lastly, simply set the configuration to the view controller’s contentUnavailableConfiguration
.
self.contentUnavailableConfiguration = config
The empty state shown below should now be visible at the center of the view controller.
You can refer to the UIContentUnavailableConfiguration
documentation to find out all the other configurable content.
2. Using the Predefined Loading Configuration
If we need to show an empty state while waiting for your apps to load, then we can use the predefined loading configuration.
var config = UIContentUnavailableConfiguration.loading()
self.contentUnavailableConfiguration = config
Using the above code will yield the following empty state:
Just like an empty configuration, the loading configuration’s placeholder content is also customizable.
var config = UIContentUnavailableConfiguration.loading()
config.text = "Fetching content. Please wait..."
config.textProperties.font = .boldSystemFont(ofSize: 18)
self.contentUnavailableConfiguration = config
Here’s what it looks like after the above customization:
3. Using the Predefined Search Configuration
Another very useful predefined configuration is the search configuration. We can use it when want to show an empty state for a search result:
var config = UIContentUnavailableConfiguration.search()
self.contentUnavailableConfiguration = config
Similar to the loading configuration, the search configuration’s placeholder content is also customizable.
4. Using UIHostingConfiguration
Lastly, my personal favorite is to use the UIHostingConfiguration
. This approach essentially enables us to create any empty state layout that we want using SwiftUI.
For example:
let config = UIHostingConfiguration {
Text("Unknown error occurred, please [contact support](https://swiftsenpai.com).")
.multilineTextAlignment(.center)
}
self.contentUnavailableConfiguration = config
The above code will give us the following outcome:
Pro Tip:
To learn more about
UIHostingConfiguration
, I recommend checking out these articles.
Updating the View Controller’s contentUnavailableConfiguration
When it comes to updating the contentUnavailableConfiguration
, Apple recommends developers to override a new update method called updateContentUnavailableConfiguration(using:)
.
As of now, there is no official documentation specifying when exactly this method will be called. However, based on my observation, it appears that the method gets triggered every time the view controller is loaded.
In situations where we need to manually trigger the update method, we can call the following function:
setNeedsUpdateContentUnavailableConfiguration()
A Real-Life Use Case
Based on what we have just discussed, I have created a sample app to showcase how to leverage the UIContentUnavailableConfiguration
to display an empty state when the app is either loading or encounters an error.
To further enhance the interactivity of the sample app, I have also added a reload button (which is also part of the UIContentUnavailableConfiguration
) in the error empty state. This reload button enables users to easily refresh the app’s content in the event of an error.
Here’s the full sample code if you’re interested. Be sure to run it on Xcode 15 beta 1 or later.
import UIKit
class ContentUnavailableViewController: UIViewController {
/// Variable to keep track of content fetching state
/// nil means content is not yet fetched
var fetchContentSuccessful: Bool? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Create fetch button
let fetchAction = UIAction(handler: { [weak self]_ in
self?.startFetchCotent()
})
let fetchButton = UIBarButtonItem(title: "Fetch", primaryAction: fetchAction)
navigationItem.rightBarButtonItem = fetchButton
}
override func updateContentUnavailableConfiguration(
using state: UIContentUnavailableConfigurationState
) {
// Remove existing configuration (if exist)
contentUnavailableConfiguration = nil
guard let fetchContentSuccessful else {
// User have not trigger fetch
return
}
if fetchContentSuccessful {
// Prompt alert view
showContent()
} else {
// Show empty state
showError()
}
}
/// Start the fetch content flow
private func startFetchCotent() {
Task { [weak self] in
guard let self = self else { return }
showLoading()
fetchContentSuccessful = await fetchContent()
// Update UI
setNeedsUpdateContentUnavailableConfiguration()
}
}
/// Action to run after successfully fetch content
private func showContent() {
let alert = UIAlertController(
title: "🎉🎉🎉",
message: "Fetch successful!",
preferredStyle: .alert
)
let positiveAction = UIAlertAction(
title: "OK",
style: .default
)
alert.addAction(positiveAction)
present(alert, animated: true)
}
/// Show the loading empty state
private func showLoading() {
var config = UIContentUnavailableConfiguration.loading()
config.text = "Fetching content. Please wait..."
config.textProperties.font = .boldSystemFont(ofSize: 18)
self.contentUnavailableConfiguration = config
}
/// Show the empty state when encounter error
private func showError() {
var errorConfig = UIContentUnavailableConfiguration.empty()
errorConfig.image = UIImage(systemName: "exclamationmark.circle.fill")
errorConfig.text = "Something went wrong."
errorConfig.secondaryText = "Please try again later."
// Create configuration for reload button
var retryButtonConfig = UIButton.Configuration.borderless()
retryButtonConfig.image = UIImage(systemName: "arrow.clockwise.circle.fill")
errorConfig.button = retryButtonConfig
// Define the reload button action
errorConfig.buttonProperties.primaryAction = UIAction.init(handler: { _ in
Task { [weak self] in
guard let self = self else { return }
startFetchCotent()
}
})
contentUnavailableConfiguration = errorConfig
}
/// A dummy function to simulate the fetch content action
private func fetchContent() async -> Bool {
// Sleep for 1 minutes to simulate a slow API call
try? await Task.sleep(nanoseconds: 1_000_000_000)
return Bool.random()
}
}
Wrapping Up
I really like this improvement in UIKit, it addresses an aspect that has long been neglected by developers — the empty state.
By reducing the friction associated with handling empty states, Apple effectively eliminates one of the common excuses developers have had for neglecting this aspect of app design, encouraging developers to take responsibility for ensuring that their app’s UIs are not left in a bleak, unhandled state.
If you enjoy reading this article, feel free to check out my other iOS development related articles. You can also follow me on Twitter and LinkedIn, and subscribe to my newsletter so that you won’t miss out on any of my upcoming iOS development-related articles.
Thanks for reading. 👨🏻💻
👋🏻 Hey!
While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.
- ✨ Bartender: Superpower your menu bar and take full control over your menu bar items.
- ✨ CleanShot X: The best screen capture app I’ve ever used.
- ✨ PixelSnap: Measure on-screen elements with ease and precision.
- ✨ iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.