Skip to content

How to use ContentUnavailableView in SwiftUI | SwiftUI Bootcamp #74

Empty states are some of the most commonly overlooked UI in apps — no results, no connection, no data. ContentUnavailableView, introduced in iOS 17, is Apple's standardized component for these moments, giving you a consistent look that matches system apps like Mail and Files with minimal code.

What You'll Learn

  • How to use ContentUnavailableView for custom empty states (no internet, no results, etc.)
  • How to use the built-in ContentUnavailableView.search(text:) factory for search empty states
  • How to handle OS availability correctly using if #available(iOS 17.0, *)

Mental Model

Imagine a storefront display window. When the store has products, the window shows them beautifully. When the store is closed or restocking, a well-designed "Be right back" sign appears in the window — not just an empty, confusing glass pane. ContentUnavailableView is that well-designed sign: it tells users what's missing, why, and (optionally) what they can do about it, using a consistent layout that iOS users already recognize from Apple's own apps.

The alternative — showing nothing — is worse than even a minimal empty state. ContentUnavailableView makes it easy to handle this case properly every time.

Detailed Explanation

ContentUnavailableView is an iOS 17 API. It has two primary usage patterns: a custom initializer and a factory method for search.

The custom initializer ContentUnavailableView("No Internet Connection", systemImage: "wifi.slash", description: Text(...)) creates a vertically centered layout with an icon, a bold title, and a description. The systemImage parameter accepts any SF Symbols name. The description parameter is a Text view, so it supports .bold(), .foregroundColor(), and other text modifiers.

ContentUnavailableView.search(text:) is a factory for the specific case of an empty search result. Pass the current search text and it renders a system-styled "No Results for 'query'" view that matches the native style used in Spotlight and other Apple apps.

Because this is an iOS 17+ API, you must wrap it in if #available(iOS 17.0, *). The else branch provides your fallback for older OS versions. For apps targeting iOS 17 as minimum deployment, you can skip the availability check entirely.

The ContentUnavailableView fills its proposed space — put it where the missing content would have been. If a List would normally be here when data is available, replace the List with ContentUnavailableView when the data array is empty.

Code Structure

ContentUnavailableViewBootcamp.swift is intentionally minimal — it shows the view directly with if #available gating. The commented-out .search(text:) factory variant is preserved so you can swap in either pattern. The #Preview macro (iOS 17 style) is used instead of the older PreviewProvider.

Complete Code

ContentUnavailableViewBootcamp.swift

swift
import SwiftUI

struct ContentUnavailableViewBootcamp: View {
    var body: some View {
        if #available(iOS 17.0, *) {
//            ContentUnavailableView.search(text: "abc") // Factory for "No Results for 'abc'" search empty state
            ContentUnavailableView(
                "No Internet Connection",        // Bold title shown below the icon
                systemImage: "wifi.slash",       // SF Symbol displayed prominently above the title
                description: Text("Please connect to the internet and try again.") // Subtitle text
            )
        } else {
            // Fallback on earlier versions
            // FakeContentUnavailableView() // Swap in your own custom empty state for iOS 16 and below
        }
    }
}

#Preview {
    ContentUnavailableViewBootcamp()
}

Code Walkthrough

  1. if #available(iOS 17.0, *) — This compile-time check ensures ContentUnavailableView is only called on iOS 17 and later. Without this check, your app crashes on iOS 16. The * covers all other platforms (Mac, watchOS, etc.) — they'll use the same branch if they also support iOS 17+.

  2. ContentUnavailableView.search(text: "abc") (commented out) — The factory variant generates a standard "No Results for 'abc'" layout. Pass your live searchText state variable here in a real search screen, so the message reflects exactly what the user typed.

  3. ContentUnavailableView("No Internet Connection", systemImage: "wifi.slash", description: Text(...)) — The three-parameter initializer. The first parameter is the bold headline. systemImage: accepts any SF Symbols identifier — "wifi.slash", "doc.text.magnifyingglass", "tray", etc. The description: is a Text with supporting context.

  4. else { } block — This is where you put your custom fallback for iOS 16 and below. Leaving it empty (as shown) means iOS 16 users see nothing — which is why // FakeContentUnavailableView() is suggested as a comment. Build a simple equivalent in a separate view and use it here.

  5. #Preview { ... } — The new Swift macro for previews, available from Xcode 15. It's shorter than the older PreviewProvider struct pattern and doesn't require a type name. Both patterns compile; this is the modern convention.

Common Mistakes

Mistake: Forgetting the if #available(iOS 17.0, *) check
ContentUnavailableView is iOS 17 only. Removing the availability check will cause a compile error if your deployment target is lower than iOS 17, and a crash at runtime on older devices if the check is bypassed. Always include the availability guard unless your minimum deployment target is iOS 17+.

Mistake: Showing ContentUnavailableView only for errors, not for empty data
This view is meant for any situation where expected content is absent — not just errors. Show it when a search returns no results, when a list is empty on first launch, or when a category has no items. Don't reserve it only for network errors.

Mistake: Placing ContentUnavailableView in a fixed-size container
ContentUnavailableView works best when given flexible space — it centers its icon and text vertically and horizontally. Wrapping it in a constrained frame can clip the icon or push content off-center. Give it .frame(maxWidth: .infinity, maxHeight: .infinity) if you need it to fill a specific area.

Key Takeaways

  • ContentUnavailableView requires iOS 17+ — always guard with if #available(iOS 17.0, *) unless your minimum deployment target is iOS 17
  • Use ContentUnavailableView.search(text: searchQuery) for search empty states; use the three-parameter initializer for custom empty states with your own icon and message
  • Show it wherever the absent content would normally appear — as a replacement for an empty List, ScrollView, or detail area

Last updated: June 27, 2026

Released under the MIT License.