3️⃣Building Widgets
Fall 2023 | Reade Plunkett
WidgetKit
WidgetKit is the framework developed by Apple that allows us to build widgets, watch complications, and Live Activities. It allows contents of our app to be available in contexts outside the app and extend its reach by building an ecosystem of glanceable, up-to-date experiences.
Widget Timeline
While Widgets use SwiftUI to display their content, WidgetKit is used to render their view. Even if your widget is on screen, it is not necessarily active at all times. Since reloading a widget consumes system resources and can lead to battery drain, WidgetKit limits the frequency and number of updates you request to what’s necessary.
Each widget is assigned a budget which is dynamically allocated and considers the following factors:
The frequency and times the widget is visible to the user
The widget’s last reload time
Whether the widget’s containing app is active
Typically a widget can be allocated 40 - 70 refreshes as a daily budget, corresponding to the widget reloading about every 15 - 60 minutes.
We can define a timeline for when our widget should update if the widget has predictable points in time where it makes sense to update its content. For example, a widget that displays weather information might update the temperature hourly throughout the day. A stock market widget could update its content frequently during open market hours, but not at all over the weekend. By planning these times in advance, WidgetKit automatically refreshes your widget when the appropriate time arrives.
When you define your widget, you implement a custom [TimelineProvider]
. WidgetKit gets a timeline from your provider, and uses it to track when to update your widget. A timeline is an array of [TimelineEntry]
objects. Each entry in the timeline has a date and time, and additional information the widget needs to display its view. In addition to the timeline entries, the timeline specifies a refresh policy that tells WidgetKit when to request a new timeline.
Model
Before we start creating our widget, we will create a Weather
Model that will contain the data for our weather. The model contains the following information:
conditions
: A string describing the weather conditions.symbol
: A string referencing an SF Symbol that depicts the conditions.color
: The background color name for these weather conditions.temp
: The current temperature
We also define six constant weather conditions within our model that we will reference through this project corresponding to sunny
, cloudy
, overcast
, rainy
, lightning
, and snowy
.
TimelineEntry
Next, we will define TimelineEntry
for our weather widget. This type specifies the date to display a widget, and, optionally, indicates the current relevance of the widget’s content.
Here, we define an entry that contains the date as well as a weather model that corresponds to this entry. We will use the data in the weather model to populate our Widget’s content view.
TimelineProvider
Periodically, our widgets will need to update their display. For example, a sports widget could update its display every time a team scores, a weather widget could update when the conditions change, or a music widget could update when the song playing finishes. The TimelineProvider is a type that advises WidgetKit when to update a widget’s display.
WidgetKit requests a timeline from the provider. This timeline is an array of objects that conform to TimelineEntry
. Each entry contains a date, as well as additional properties we can define for displaying the widget.
Within the Provider
, we have three functions to handle each of the three ways WidgetKit can request timeline entries: placeholder
, getSnapshot
, andgetTimeline
placeholder
: returns an entry representing a placeholder version of the widget.getSnapshot
: returns single immediate snapshot entry that represents the widget’s current state.getTimeline
: returns a timeline of entries, including the current moment and any future dates when the widget’s state will change.
Fetch Placeholder Entry
When WidgetKit displays our widget for the first time, it renders the widget’s view as a placeholder. This placeholder provides a generic representation of your widget, giving the user a general idea of what the widget shows.
Fetch Snapshot Entry
WidgetKit calls getSnapshot
when the widget appears in transient situations, such as when the user is adding a widget. The context
parameter provides additional details how the entry should be use, for example, whether it is a preview inside the Widget Gallery, the family, or the size of the widget to display.
In this snapshot, we create and return an entry that contains the current date and time, as well as a random Weather
object. It is important to note, if the data for the snapshot requires a significant amount of time to load (for example, making a network call), it is best practice to use sample data.
Fetch Timeline
After a user adds our widget from the widget gallery, WidgetKit makes the timeline request. Our widget extension will not always be running, so WidgetKit needs to know when to activate to update the widget. The timeline tells WidgetKit when you would like to update the widget.
Here, we create and return a timeline of five entries each an hour apart. Each entry contains a date as well as a random weather. We use a refresh policy .atEnd
to tell WidgetKit to request a new timeline after the last date specified by the entries in the timeline.
Widget
Let’s first start by building the Widget itself by conforming to the Widget
protocol. This protocol contains two components:
kind
: This is a string that identifies the widget.body
: This is body of the widget that defines its contents and configuration.
A Widget can have two configurations: StaticConfiguration
or AppIntentConfiguration
.
StaticConfiguration
: Describes the content of a widget that has no user-configurable options.AppIntentConfiguration
: Describes the content of a widget that uses custom intent to provide user-configurable options.
We pass into the configuration the widget’s kind as well as its TimelineProvider
. WidgetKit invokes the content closure, passing a timeline entry created by the widget’s provider. This entry can either be a snapshot
entry or one from the timeline
. Once we have this entry, we can pass it into the Widget’s view to display its contents.
For a widget, we have a view notable modifiers:
configurationDisplayName
: Sets the name shown for a widget when a user adds or edits it.description
: Sets the description shown for a widget when a user adds or edits it.supportedFamilies
: Sets the sizes that a widget supports. There are two families our widget can belong in: system or accessory.System Family
systemSmall
: A small system widget that can appear on the Home Screen, Today View, or in StandBy, or the Mac Desktop.systemMedium
: A medium system widget that can appear on the Home Screen, Today View, or in StandBy, or the Mac Desktop.systemLarge
: A large system widget that can appear on the Home Screen, Today View, or in StandBy, or the Mac Desktop.systemExtraLarge
: An extra large system widget that can appear on the Home Screen, Today View, StandBy, or the Mac Desktop.
Accessory Family
accessoryCircular
: A circular widget that can appear as a complication in watchOS, or on the Lock Screen.accessoryCorner
: A widget-based complication in the corner of a watch face in watchOS.accessoryRectangular
: A rectangular widget that can appear as a complication in watchOS, or on the Lock Screen.accessoryInline
: An inline widget that can appear as a complication in watchOS, or on the Lock Screen.
View
Next, we create the View for our widget using SwiftUI. The Widget’s entry gets fetched from the TimelineProvider
and passed into the View
by the Widget’s StaticConfiguration
.
We can access the Weather
object within our Entry
to display the temperature, conditions, symbol, and background color.
Additionally, we can make use of the canvas and live view to easily and quickly view our changes. We can even specify which size Widget (or WidgetFamily) we want to see displayed in the preview:
Here, we create an entry in the timeline for each one of our weather conditions. We can view and cycle through this timeline in the preview.
WidgetBundle
In the previous section, we discussed creating a new widget extension. We can define multiple widgets within a single widget extension. A widget bundle allows us to expose these multiple widgets. In this case however, we only expose a single widget.
Last updated