DEV Community

Cover image for Empty List placeholder. SwiftUI
Anton Paliakou
Anton Paliakou

Posted on

Empty List placeholder. SwiftUI

Year twenty twenty-one. Almost a month has passed since the WWDC. As usual Apple presented many amazing features/updates 😍. As expected updated SwiftUI framework. But did not add placeholder for List view 😭. It's not big deal, but it was one of my expectations from conference. Okay let's do it ourselves đŸ’Ș

The List is one of the most used view in apps.
When using the List, devs also must handle the state of an empty data range and show a placeholder.

As an example, consider a simple list of countries. Show placeholder when data is empty.

Countries

Placeholder

Country model:

struct Country: Identifiable {
    let id = UUID()
    let name: String
}
Enter fullscreen mode Exit fullscreen mode

So, have any ideas on how to implement a placeholder?

First idea If else

The first thing that comes to mind it's if else conditional statement.

struct ContentView: View {

    @State var countries: [Country] = [] // Data source

    var body: some View {
        if countries.isEmpty {
            Text("No Countries") // Placeholder
                .font(.largeTitle)
        } else {
            List(countries) { country in // List countires
                Text(country.name)
                    .font(.title)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • the most simple and clear way
  • easy to modify
  • it works and shows the placeholder when needed
  • easy to use any view for placeholder

Disadvantages:

  • the code looks cumbersome
  • not reusable

It works and sometimes it's enough. But in production, it would be nice to have a component that implements the logic of displaying a placeholder inside the component. So, goes to the next idea.

Second idea EmptyList

Improve if else idea and move logic show/hide placeholder to custom view, call it EmptyList:


struct EmptyList<Items: RandomAccessCollection, ListRowView: View, PlaceholderView: View>: View where Items.Element: Identifiable {

    private let items: Items
    private let listRowView: (Items.Element) -> ListRowView
    private let placeholderView: () -> PlaceholderView

    /// - Parameters:
    ///   - items: Source data for List. Item must implement Identifiable protocol
    ///   - listRowView: View displayed for each source Item
    ///   - placeholderView: Placeholder. View displayed when the items collection isEmpty
    init(_ items: Items,
         @ViewBuilder listRowView: @escaping (Items.Element) -> ListRowView,
         @ViewBuilder placeholderView: @escaping () -> PlaceholderView) {
        self.items = items
        self.listRowView = listRowView
        self.placeholderView = placeholderView
    }

    var body: some View {
        if !items.isEmpty {
            List { // List countires
                ForEach(items) { item in
                    self.listRowView(item)
                }
            }
        } else {
            placeholderView()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Using the EmptyList is very easy. First parameter - data source, second parameter - list row view, and finally third parameter - placeholder view.


struct ContentView: View {

    @State var countries: [Country] = [] // Data source

    var body: some View {
        EmptyList(countries, // Data items 
        listRowView: { country in // List row view
            Text(country.name)
                .font(.title)
        }, placeholderView: {
            Text("No Countries") // Placeholder
                .font(.largeTitle)
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • code looks clean and clear 😍
  • easy to modify custom view
  • reusable in project
  • use any view for placeholder

Disadvantages:

  • list is embedded in EmptyList view, and if want to add some ViewModifier-s to the list, need for more efforts and modify code

Usually, I would have to say that this is all and say goodbye but is not all 😎. I want to share an idea of how I cook placeholder for lists in my projects.

Preferred idea ViewModifier

Create custom ViewModifier to manage placeholder, call it EmptyDataModifier:


struct EmptyDataModifier<Placeholder: View>: ViewModifier {

    let items: [Any]
    let placeholder: Placeholder

    @ViewBuilder
    func body(content: Content) -> some View {
        if !items.isEmpty {
            content
        } else {
            placeholder
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Uses EmptyDataModifier:


struct ContentView: View {

    @State var countries: [Country] = [] // Data source

    var body: some View {
        List(countries) { country in
            Text(country.name)
                .font(.title)
        }
        .modifier(EmptyDataModifier(
            items: countries,
            placeholder: Text("No Countries").font(.title)) // Placeholder
        ) 
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it! Also via extension can little bit improve the solution and limited apply EmptyDataModifier only for List.

extension List {

    func emptyListPlaceholder(_ items: [Any], _ placeholder: AnyView) -> some View {
        modifier(EmptyDataModifier(items: items, placeholder: placeholder))
    }
}
Enter fullscreen mode Exit fullscreen mode
struct ContentView: View {

    @State var countries: [Country] = [] // Data source

    var body: some View {
        List(countries) { country in
            Text(country.name)
                .font(.title)
        }
        .emptyListPlaceholder(
            countries,
            AnyView(ListPlaceholderView()) // Placeholder 
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • code look clean and clear 😍 😍 😍
  • no need to create a custom List view
  • easy to modify
  • reusable in project
  • use any view for placeholder
  • this way for can be used for any view placeholder

Disadvantages:

  • no (subjective opinion)

Instead of summary

In my opinion, the most suitable way to implement a placeholder is to use a custom ViewModifier.
I'm sure sooner or later the Apple will add a placeholder for the List view. Maybe this article will be as a request for this feature for Apple. Who knows.

Source code

Thanks for reading! See you soon.

Top comments (3)

Collapse
 
kassi profile image
Karsten SilkenbÀumer

I made the same mistake at first: Placing if/else and either show placeholder or List won't work anymore as soon as you make the list "searchable". If you start typing and nothing is found, no way of correcting your input because: no list, no search text field. So better not hide the list.
I came here on my search to find a way to set the background of the area the empty list occupies (sounds silly, but it's like that).

Collapse
 
cneily profile image
Cneily

Awesome. Thanks for sharing with the rest of us!

Collapse
 
toni777772 profile image
Anton Paliakou

Thank You)