You are currently viewing Getting Started With WidgetKit

Getting Started With WidgetKit

WidgetKit was first introduced in iOS 14 as a simple framework whereby developers can leverage to create a Home Screen Widget. Since then, it has slowly evolved into a framework that powers some of the most anticipated features in iOS 16, namely Lock Screen Widget, Live Activities, and Dynamic Island.

It is safe to say that WidgetKit has now become one of the must-learn frameworks for all the iOS developers out there. Therefore, I have decided to create a series of articles that center around WidgetKit and other related topics. This article will be the first in the series, so let’s take things one step at a time by looking into the basics you need to know to get started creating your first Home Screen Widget.

There is a lot to unpack here, so let’s get started.


The View Size Widget

In this article, we will build a simple Home Screen Widget that displays the dimension of the widget. On top of that, the widget will also display the timeline provider information which we will take a closer look at later on.

Static Home Screen Widget example in iOS
The View Size Widget

Adding a Widget Extension

All iOS widgets must tie to an iOS app. Once you have your iOS app created in Xcode, go ahead and add a new widget extension target and name it “ViewSizeWidget”. For simplicity’s sake, leave “Include Configuration Intent” unchecked as it is beyond the scope of this article. I will be covering that in detail in a future article, so please stay tuned.

Adding widget extension target in Xcode
Adding widget extension target
Uncheck "Include Configuration Intent" in Xcode
Uncheck “Include Configuration Intent”

Upon adding the new target, Xcode will prompt you to activate the scheme for the widget extension. Go ahead and activate it.

At this stage, you should notice that a folder with a Swift file (ViewSizeWidget.swift) has been added to your Xcode workspace.

Folder generated for widget extension in Xcode
Folder generated for widget extension

Go ahead and delete all the auto-generated code in ViewSizeWidget.swift. In the following sections of this article, I will guide you through the entire implementation process of ViewSizeWidget.swift.


Creating a Widget

A widget consist of 4 main components:

  • The Timeline Entry
  • The Widget View (SwiftUI View)
  • The Timeline Provider
  • The Widget Configuration

Let’s implement each of these components one-by-one.

The Timeline Entry

You can imagine the timeline entry as the model object of the widget view. It must at least consist of a date parameter. Any other parameters can also be added to the timeline entry based on our own needs.

The system will then use the date specified in the timeline entry to determine when to show/refresh the data in the widget.

For our view size widget, let’s name our timeline entry ViewSizeEntry.

import WidgetKit
import SwiftUI

struct ViewSizeEntry: TimelineEntry {
    let date: Date
    let providerInfo: String
}

Notice that we will use the providerInfo to hold the information related to the timeline provider and show it on the widget — more on that later.

The Widget View

After creating our timeline entry, we can proceed to implement the Widget’s UI. It is basically just a SwiftUI View.

struct ViewSizeWidgetView : View {
   
    let entry: ViewSizeEntry

    var body: some View {
        GeometryReader { geometry in
            VStack {
                
                // Show view size
                Text("\(Int(geometry.size.width)) x \(Int(geometry.size.height))")
                    .font(.system(.title2, weight: .bold))
                
                // Show provider info
                Text(entry.providerInfo)
                    .font(.footnote)
            }
            .containerBackground(for: .widget) {
                Color.green
            }
        }
    }
}

There is nothing fancy about the code above. But I do like to draw your attention to the entry parameter. Notice how we are using it to show the data (providerInfo) in our timeline entry onto the widget UI.

Also, notice how we are using the containerBackground(for:alignment:content:) view modifier to define the background of the widget view. You can find out more here.

The Timeline Provider

As the name implies, the timeline provider provides information to the system about what is to be shown on the widget at a specific timestamp. As you might have expected, the timeline provider must conform to the TimelineProvider protocol which has the following 3 method requirements.

struct ViewSizeTimelineProvider: TimelineProvider {
    
    typealias Entry = ViewSizeEntry
    
    func placeholder(in context: Context) -> Entry {
        // Implementation here...
    }

    func getSnapshot(in context: Context, completion: @escaping (Entry) -> ()) {
        // Implementation here...
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        // Implementation here...
    }
}

Also, be sure to alias Entry to the ViewSizeEntry we defined not long ago.

Now let’s go ahead and implement these 3 methods one-by-one.

First off is the placeholder(in:) method. It is essentially providing dummy data to the system to render a placeholder UI while waiting for the widget to get ready. Notice that SwiftUI will apply a redaction effect to the dummy data we provide, thus the actual value of the dummy data is not important. Here’s the implementation:

func placeholder(in context: Context) -> Entry {
    // This data will be masked
    return ViewSizeEntry(date: Date(), providerInfo: "placeholder")
}

which will yield the following placeholder UI:

iOS Home Screen widget in placeholder state
Widget in placeholder state

Next up is the getSnapshot(in:completion:) method. This function mainly provides the data required by the system to render the widget in the widget gallery. Following is the implementation:

func getSnapshot(in context: Context, completion: @escaping (Entry) -> ()) {
    let entry = ViewSizeEntry(date: Date(), providerInfo: "snapshot")
    completion(entry)
}
Widget snapshot in iOS widget gallery
Widget snapshot in widget gallery

From the screenshot above, you can see that the widget is actually showing the providerInfo we provided — “snapshot”.

Lastly is the getTimeline(in:completion:) method. This is the most important method in the timeline provider as it provides an array of timeline entries for the current time and, optionally, any future times to update a widget.

Since our widget is just showing static data all the time, we can simply return 1 timeline entry like so.

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    let entry = ViewSizeEntry(date: Date(), providerInfo: "timeline")
    let timeline = Timeline(entries: [entry], policy: .never)
    completion(timeline)
}

With all that in place, we can finally proceed to configure the widget.

The Widget Configuration

The widget configuration is where we put everything we just implemented together. Let’s take a look at the implementation and I will go through the details later on.

@main
struct ViewSizeWidget: Widget {
    let kind: String = "ViewSizeWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: ViewSizeTimelineProvider()) { entry in
            ViewSizeWidgetView(entry: entry)
        }
        .configurationDisplayName("View Size Widget")
        .description("This is a demo widget.")
        .supportedFamilies([
            .systemSmall,
            .systemMedium,
            .systemLarge,
        ])
    }
}

The first thing to notice here is the @main attribute. It indicates that the ViewSizeWidget is the entry point for our widget extension, implying that the extension contains a single widget. For cases where we have multiple widgets, which is beyond the scope of this article, we will have to mark a widget bundle as an entry point.

The main purpose of a widget configuration is to link up the timeline provider and the widget view. The kind of configuration we are using here is StaticConfiguration. Using StaticConfiguration means that our widget does not have any user-configurable properties. We will need to use IntentConfiguration instead if we would to add any user-configurable properties to our widget.

There are various types of modifiers we can use to further configure the widget configuration. For example, we can use the configurationDisplayName and description modifier to configure the widget gallery title and subtitle.

The widget gallery title and subtitle in iOS
The widget gallery title and subtitle

Lastly, the supportedFamilies modifier allows us to specify the widget size that we want to support. For our view size widget, we will be supporting small, medium, and large sizes. Note that there are other widget families such as accessoryCircular, accessoryRectangular, etc. that are mainly used for creating Lock Screen Widgets. I will be covering them in depth in an article about Lock Screen Widgets, so make sure to keep an eye out!

With all these in place, we have completed the view size widget implementation. Now go ahead and run the app using the widget extension scheme to see everything in action.

The full sample code is available here.


Wrapping Up

What you’ve learned in this article are the very basics of WidgetKit, there is still a lot more to cover and explore. Be sure to expect more articles with topics surrounding WidgetKit coming soon to this site. Feel free to follow me on Twitter and subscribe to my newsletter so that you won’t miss out on any of my upcoming articles.

Thanks for reading. 👨🏻‍💻


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.