Widgetkit Series 4-5: Creating a Date and Emoji Widget in SwiftUI

In this tutorial, we'll walk through the process of creating a custom widget for iOS using SwiftUI and WidgetKit. Our widget will display the current date along with a corresponding emoji for each day of the week. Let's break down the code and explain each part in detail.

Setting up the Provider

We start by creating a Provider struct that conforms to the TimelineProvider protocol. This provider is responsible for generating the timeline of our widget's content.

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> DateEntry {
        DateEntry(date: Date())
    }

    func getSnapshot(in context: Context, completion: @escaping (DateEntry) -> ()) {
        let entry = DateEntry(date: Date())
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [DateEntry] = []

        let currentDate = Date()
        for dayOffset in 0 ..< 7 {
            let entryDate = Calendar.current.date(byAdding: .day, value: dayOffset, to: currentDate)!
            let startOfDate = Calendar.current.startOfDay(for: entryDate)

            let entry = DateEntry(date: startOfDate)

            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

The getTimeline function generates a timeline of seven entries, one for each day of the week, starting from the current date.

Creating the DateEntry

Next, we define a DateEntry struct that conforms to the TimelineEntry protocol. This struct represents the data for each widget update.

struct DateEntry: TimelineEntry {
    let date: Date

    var emoji: String {
        let weekday = Calendar.current.component(.weekday, from: date)
        switch weekday {
        case 1: return "😎" // Sunday
        case 2: return "😫" // Monday
        case 3: return "😊" // Tuesday
        case 4: return "🤓" // Wednesday
        case 5: return "🤔" // Thursday
        case 6: return "🥳" // Friday
        case 7: return "😴" // Saturday
        default: return "🤖"
        }
    }
}

The emoji computed property returns a different emoji based on the day of the week.

Designing the Widget View

The WidgetTestWidgetEntryView struct defines the layout and appearance of our widget.

struct WidgetTestWidgetEntryView : View {
    var entry: DateEntry

    var body: some View {
        ZStack {
            ContainerRelativeShape()
                .fill(LinearGradient(
                    gradient: Gradient(colors: [.cyan, .green]),
                    startPoint: .topLeading,
                    endPoint: .bottomTrailing
                ))

            VStack {
                Text("\(entry.emoji)")
                    .font(.system(size: 50))
                Text(entry.date.formatted(.dateTime.weekday(.wide)))
                    .font(.title3)
                    .fontWeight(.bold)
                    .foregroundStyle(.white.opacity(0.7))

                Text(entry.date.formatted(.dateTime.day()))
                    .font(.largeTitle)
                    .bold()
                    .foregroundStyle(.white.opacity(0.7))
            }
            .padding()
        }
    }
}

This view displays the emoji, weekday name, and day of the month on a gradient background.

Configuring the Widget

Finally, we create the WidgetTestWidget struct to configure our widget.

struct WidgetTestWidget: Widget {
    let kind: String = "WidgetTestWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            if #available(iOS 17.0, *) {
                WidgetTestWidgetEntryView(entry: entry)
                    .containerBackground(.fill.tertiary, for: .widget)
            } else {
                WidgetTestWidgetEntryView(entry: entry)
                    .padding()
                    .background(Color.black)
            }
        }
        .configurationDisplayName("Our Test Widget")
        .description("This is Our Test Widget for date and emoji")
        .contentMarginsDisabled()
        .supportedFamilies([.systemSmall])
    }
}

This configuration sets up the widget with a display name, description, and supported size (small).

Preview

To help with development, we've included a preview that shows how the widget looks for each day of the week:

#Preview(as: .systemSmall) {
    WidgetTestWidget()
} timeline: {
    DateEntry(date: Calendar.current.date(from: DateComponents(year: 2024, month: 9, day: 1))!) // Sunday
    DateEntry(date: Calendar.current.date(from: DateComponents(year: 2024, month: 9, day: 2))!) // Monday
    DateEntry(date: Calendar.current.date(from: DateComponents(year: 2024, month: 9, day: 3))!) // Tuesday
    DateEntry(date: Calendar.current.date(from: DateComponents(year: 2024, month: 9, day: 4))!) // Wednesday
    DateEntry(date: Calendar.current.date(from: DateComponents(year: 2024, month: 9, day: 5))!) // Thursday
    DateEntry(date: Calendar.current.date(from: DateComponents(year: 2024, month: 9, day: 6))!) // Friday
    DateEntry(date: Calendar.current.date(from: DateComponents(year: 2024, month: 9, day: 7))!) // Saturday
}

This preview allows you to see how your widget will look for each day of the week without having to wait for the actual days to pass.

And there you have it! A fun and informative widget that displays the current date with a matching emoji. Feel free to customize the colors, fonts, and emojis to suit your preferences.