Every experienced iOS Developer knows that a quality application shines not only when it is full of data, images, and dynamic content, but also when there is absolutely nothing to show. Properly handling the absence of data is a fundamental pillar of User Experience (UX) design. Historically, in Swift programming, handling these empty screens required building custom views from scratch. However, Apple has revolutionized this practice by introducing a native and elegant solution.
In this tutorial, we will explore in depth what they are and how to display Empty States with ContentUnavailableView in SwiftUI, an indispensable tool for any developer working within the Apple ecosystem. We will learn how to implement this view not just for the iPhone, but by writing Swift code that adapts perfectly to iOS, macOS, and watchOS using Xcode.
What are Empty States and why are they crucial?
Empty States are those screens in your application where, for various reasons, there is no content to show the user. These situations typically occur for three main reasons:
- First use (Onboarding): The user has just downloaded the app and hasn’t created any content yet (for example, a to-do list with no registered tasks).
- User or system errors: There is no internet connection, or a search yielded no results.
- Completed actions: The user has cleared their inbox and there are no more emails to read.
For an iOS Developer, ignoring these states is a serious mistake. A blank screen with no context causes frustration; the user might think the application has frozen, is loading infinitely, or is simply broken. A good Empty State must fulfill three objectives:
- Inform: Clearly explain why the screen is empty.
- Educate: Teach the user how to populate that screen.
- Incentivize: Provide a clear button or Call to Action to resolve the empty state.
The Past: Swift Programming before iOS 17
Before the recent SwiftUI updates, creating an empty state involved writing a lot of boilerplate code in Xcode. You had to combine a VStack, an Image with an SF Symbols icon, several Text views with specific typography (.font(.title), .foregroundColor(.secondary)), and manage the spacing manually.
Although Swift programming made creating reusable components easier, every development team ended up reinventing their own wheel. This not only consumed time but often resulted in visual inconsistencies within the same application or when compared to the operating system.
The Modern Solution: ContentUnavailableView in SwiftUI
Starting with iOS 17, macOS 14, and watchOS 10, Apple introduced ContentUnavailableView. This is a native SwiftUI view specifically designed to standardize and simplify the creation of empty states.
By using Empty States with ContentUnavailableView in SwiftUI, you delegate the responsibility of margins, font sizes, semantic colors, and adaptability to the operating system. The system knows exactly what a “No content” message should look like on a giant Mac screen, an iPhone screen, or the tiny face of an Apple Watch.
Basic implementation in Xcode
The easiest way to use this view in Xcode is through its convenience initializers. Imagine you have a notes app and the user performs a search that doesn’t match any of their entries.
import SwiftUI
struct SearchView: View {
@State private var searchText = ""
@State private var searchResults: [String] = []
var body: some View {
NavigationStack {
Group {
if searchResults.isEmpty && !searchText.isEmpty {
// This is where ContentUnavailableView shines
ContentUnavailableView(
"No results for '\(searchText)'",
systemImage: "doc.text.magnifyingglass",
description: Text("Try searching with other terms or check your spelling.")
)
} else {
List(searchResults, id: \.self) { result in
Text(result)
}
}
}
.navigationTitle("Search Notes")
.searchable(text: $searchText)
}
}
}
In this Swift code block, ContentUnavailableView takes three key parameters:
- Title: A clear and concise text string.
- System Image: An SF Symbols icon that visually reinforces the message.
- Description: Secondary text providing more context or instructions.
The SwiftUI engine will automatically apply the correct font weight to the title, use a gray/secondary color for the description, and scale the icon proportionally.
Advanced Views: Adding Actions and Customization
As an iOS Developer, you’ll often need to go beyond a simple message. You will want the user to interact and resolve the empty state. ContentUnavailableView is designed with ViewBuilders to offer maximum flexibility.
Suppose your app requires an internet connection to download a news feed, but the network request fails. Here is how you can implement an interactive empty state:
import SwiftUI
struct NewsFeedView: View {
@State private var isOffline = true
@State private var isRetrying = false
var body: some View {
if isOffline {
ContentUnavailableView {
Label("No Internet Connection", systemImage: "wifi.exclamationmark")
} description: {
Text("We couldn't load the latest news. Please check your connection and try again.")
} actions: {
Button(action: {
retryConnection()
}) {
if isRetrying {
ProgressView()
} else {
Text("Retry")
}
}
.buttonStyle(.borderedProminent)
}
} else {
// Your content view goes here
Text("News Feed Loaded")
}
}
func retryConnection() {
isRetrying = true
// Simulated network logic
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isRetrying = false
// isOffline = false (if connection is successful)
}
}
}
Analyzing the ViewBuilders:
label: Replaces the static title with any view. Using aLabelhere is standard practice, as it combines a title and an icon.description: Allows using text with advanced formatting (like partial bolding or links) or even small additional views.actions: The perfect place to put buttons. SwiftUI will stack these buttons vertically with the correct spacing according to Apple’s Human Interface Guidelines (HIG).
Predefined States in SwiftUI
Apple has included a specific predefined state for searches, as it is one of the most common use cases. If you just need a generic “No search results” state, you can use the static initializer:
ContentUnavailableView.search
You can also provide the text that was being searched so the system automatically generates the appropriate message:
ContentUnavailableView.search(text: "Cooking recipes")
This will automatically generate a fully localized interface in the user’s device language, displaying a magnifying glass icon and the text: “No results for ‘Cooking recipes'”. This saves a tremendous amount of time in Swift programming!
Cross-Platform Magic: iOS, macOS, and watchOS
One of the great advantages of SwiftUI is its “write once, run anywhere” paradigm. Implementing Empty States with ContentUnavailableView in SwiftUI is the perfect example of this.
On iOS
On the iPhone or iPad, ContentUnavailableView takes up the center of the available screen. If placed inside a NavigationStack, it will respect the large or inline navigation bar, dynamically adjusting its margins to visually sit in the “optical center” of the screen, which is usually slightly higher than the mathematical center.
On macOS
When you compile the same code in Xcode for a Mac, the behavior subtly changes to adapt to the desktop experience. Icons and fonts scale to match the macOS typographic layout (which is typically smaller and crisper). Additionally, if you have this empty state in the detail pane of a NavigationSplitView (very common in Mac apps), the background will adapt to typical macOS translucency.
On watchOS
The Apple Watch presents a unique challenge due to its screen size. An empty state design with an icon, title, description, and button could overflow the watch screen. However, by using ContentUnavailableView, SwiftUI collapses the margins, dramatically reduces the size of the systemImage, and ensures the text adapts and remains readable. As a developer, you don’t have to use endless #if os(watchOS) tags; the framework makes the right decision for you.
Best Practices for Empty State Design
To ensure you’re getting the most out of this tool in your Xcode projects, keep the following recommendations in mind:
- Don’t blame the user: If an error occurs, keep a friendly tone. Instead of “User Error: Invalid Search”, use “We couldn’t find what you’re looking for”.
- Use descriptive SF Symbols: Apple’s ecosystem includes thousands of icons. Don’t limit yourself to the magnifying glass or exclamation mark. Use a “tray” icon for empty lists or a “person silhouette” icon for an empty contact list.
- Provide a way out: Whenever possible, include the
actionsblock. An empty state without a button is a dead end in the navigation flow. Offer buttons like “Create new”, “Refresh”, or “Explore suggestions”. - Beware of Flickering: When requesting network data, make sure to have a loading state separate from the empty state. If you jump straight to evaluating the variable (which is initially empty), the user will see a
ContentUnavailableViewfor a split second before the data appears.
Conclusion
Mastering Empty States with ContentUnavailableView in SwiftUI is an essential step toward maturing as an iOS Developer. Swift programming has reached a point where the code needed to create beautiful and interactive interfaces is minimal, allowing us to focus on business logic and the user experience.