Empty State Chart in SwiftUI 4 (iOS 16 and above)

DevTechie Inc
May 11, 2023

Empty state provides an opportunity for app devs and designers to communicate state of the system.

At times app users may encounter a state where there is no data or content to display and when this happens, its app’s responsibility to communicate it to the user.

Let’s understand this better with a scenario, think of an e-commerce marketplace app where anyone can sell their products. This app has a view where user can see total product sale by day for the shop. There is one day in the week, where nothing was sold for the day.

If user is going through day to day product sales report and they run into this day when nothing was sold, they may think either the system is broken or data wasn’t received by the app because no feedback was provided by the app. This is where having empty state would be helpful.

Charts are one of the best way to communicate story behind data and having an empty state for chart is equally important.

Today, we will use newly introduced Charts Framework by Apple to build out charts to tell story behind monthly user visits on DevTechie.com as well as empty state for charts.

Let’s start with the data structure. We will create a struct which will have page visits by month for DevTechie.com.

struct DevTechieSiteStats: Identifiable {
    var id: String { UUID().uuidString }
    let month: String
    let pageVisits: Double
}

Let’s add some data for us to work with inside an extension to this struct.

extension DevTechieSiteStats {
    static let data: [DevTechieSiteStats] = [
        .init(month: "Jan", pageVisits: 2500),
        .init(month: "Feb", pageVisits: 2340),
        .init(month: "Mar", pageVisits: 5432),
        .init(month: "Apr", pageVisits: 5643),
        .init(month: "May", pageVisits: 1234),
        .init(month: "Jun", pageVisits: 6467),
        .init(month: "Jul", pageVisits: 8975),
        .init(month: "Aug", pageVisits: 2131),
        .init(month: "Sep", pageVisits: 3424),
        .init(month: "Oct", pageVisits: 5466),
        .init(month: "Nov", pageVisits: 6757),
        .init(month: "Dec", pageVisits: 2322)
    ]
}

Next, we will work on the view. Let’s start by creating two State properties. One will hold the data to be displayed on chart and other would be a boolean to mimic the empty state.

@State private var data = DevTechieSiteStats.data
@State private var showEmptyState = false

We will create Chart view and inside the view, we will have an if condition. If the showEmptyState variable is true, we will show a RuleMark with annotation to show that no page visit was recorded. In the else part, we will display a combined chart to show monthly page visits.

Chart {
    if showEmptyState {
        RuleMark(y: .value("No visits", 0))
            .annotation {
                Text("No page visit during this period.")
                    .font(.footnote)
                    .padding(10)
            }
    } else {
        ForEach(data) { item in
            LineMark(x: .value("Month", item.month), y: .value("Page Visits", item.pageVisits))
                .foregroundStyle(.indigo.gradient)
                .interpolationMethod(.catmullRom)
                .symbol(Circle())AreaMark(x: .value("Month", item.month), y: .value("Page Visits", item.pageVisits))
                .foregroundStyle(.linearGradient(colors: [.indigo, .indigo.opacity(0.1)], startPoint: .top, endPoint: .bottom))
                .interpolationMethod(.catmullRom)
        }
    }
}

While displaying the empty state, we want to hide Y axis so we will use chartYAxis modifier for that. We also want to animate between empty and non empty states so we will add animation modifier on showEmptyState value.

We will use Toggle view to toggle empty state.

Complete view code should look like this.

struct EmptyStateChart: View {
    
    @State private var data = DevTechieSiteStats.data
    @State private var showEmptyState = false
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("DevTechie.com monthly page visits")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                
                Chart {
                    if showEmptyState {
                        RuleMark(y: .value("No visits", 0))
                            .annotation {
                                Text("No page visit during this period.")
                                    .font(.footnote)
                                    .padding(10)
                            }
                    } else {
                        ForEach(data) { item in
                            LineMark(x: .value("Month", item.month), y: .value("Page Visits", item.pageVisits))
                                .foregroundStyle(.indigo.gradient)
                                .interpolationMethod(.catmullRom)
                                .symbol(Circle())
                            
                            AreaMark(x: .value("Month", item.month), y: .value("Page Visits", item.pageVisits))
                                .foregroundStyle(.linearGradient(colors: [.indigo, .indigo.opacity(0.1)], startPoint: .top, endPoint: .bottom))
                                .interpolationMethod(.catmullRom)
                        }
                    }
                }
                .chartYAxis(showEmptyState ? .hidden : .visible)
                .frame(height: 400)
                .animation(.easeInOut, value: showEmptyState)
                
                Toggle("Show Empty State", isOn: $showEmptyState)
            }
            .padding()
            .navigationTitle("DevTechie")
        }
    }
}

Build and run: