Build Your First Widget in iOS 14 With WidgetKit
By: Akashlal Bathe
Tags: Mobile, WWDC, iOS, Swift, Programming

Let’s Play!

‘TimelineProvider’

TimelineProvider is the engine of the widget — the provider class is mainly responsible for providing a bunch of views in a timeline, along with an option to give a snapshot of the widget.

For example, when a user wants to place the widget, a preview of the widget is shown to the user, which is obtained from snapshot . After being placed on the screen, timeline will return the views to be displayed on the home screen.

Since widgets load in the home screen, Apple doesn’t want users to look at a bunch of loading widgets. Hence, widgets in iOS 14 are just a bunch of views bundled in a timeline.

When the app is opened, it gives the system a bunch of views along with a time label. For example, if your app wants to show a countdown to an event in a widget, your app needs to make a bunch of views from the current time up till the event date, and it needs to tell the system which view to display at what time.

For example, if the event is four days away, the app could send in five views:

View 1: To be shown today. Has contents “Event is 4 days away.”

To be shown today. Has contents “Event is 4 days away.” View 2: To be shown tomorrow. Has contents “Event is 3 days away.”

To be shown tomorrow. Has contents “Event is 3 days away.” View 3: To be shown the day after tomorrow. Has contents “Event is 2 days away.”

To be shown the day after tomorrow. Has contents “Event is 2 days away.” View 4: To be shown a day before the event. Has contents “Event is 1 day away.”

To be shown a day before the event. Has contents “Event is 1 day away.” View 5: To be shown on the day of event. Has contents “Event has started.”

iOS displays the appropriate view based on the system time, and hence the widget ends up looking right at any point of time.

Also, creating such a bunch of views is a cheap task and doesn’t require constant compute time. Hence, this helps iOS preserve battery as well, adding to the smooth performance.

Looking at the code:

On line 14, five entries are created, one for each hour. For each entry, a view is created with Static_WidgetEntryView .

These views are then provided to the iOS system so appropriate views can be shown based on the time.

You can try testing this part out by replacing byAdding: .hour with byAdding: .minute and testing it in a simulator. After the widget is shown, for the first five minutes, the widget view will update once every minute. After this, the widget won’t change and will keep showing the last view the widget extension sent to iOS.

Designing the widget view

Now, let’s make a custom view whose contents will be rendered into the widget.

At this point, we’re going to segregate the widget view and provider since everything is in the same Swift file.

We know the TimelineProvider is responsible for building views and showing it onto a timeline. (A timeline is nothing but a time labelled series of views that gets shown on the home screen based on current time. More theory can be found in my previous article.)

The main struct, Static_Widget , is responsible for defining the widget views in the application. We’ll be moving the widget view to a new file.

Begin by creating a WidgetView Swift file by adding a new file ➡ SwiftUI View ➡ Next. Name it WidgetView . Ensure it has the widget extension checked under the target extensions, and click “Create.”

Adding a new SwiftUI view for rendering into the widget

The Swift file created would have template code for a SwiftUI view; however, we want to design a widget here. In the newly created file, import WidgetKit as well.

Now, let’s look at the widget to be created

The widget to be created

This is a funky, static widget showing a static weight and a relative time — which needs to be kept up to date.

Since the widget needs only two variables, we’ll create a struct to store them. Also, we’ll create an extension of the struct to have a preview of the data in order to easily fill in the WidgetData , just for demonstration purposes.

Now that the data structure is added, let’s prepare the view and preview for the widget. Create a WidgetView and a provider for the preview, as follows:

Writing the boilerplate

Here, you’ll notice that the preview has WidgetView previewed with .previewContext(WidgetPreviewContext(family: .systemSmall)) , which renders out the view like a widget. Here the family can be changed to systemMedium and systemLarge to preview contents in medium and large formats as well.

By adding in previews for other widget families too, the code for WidgetView gets updated to:

Begin with adding views to the body of the widget on line 17. Looking at the design, we need two labels: one for the static text “Weight” and another for the weight from data: WidgetData . So after adding two Text(“”) views in a VStack with a Spacer() in-between, lets also add font and foreground colors to make them look good.

To make the contents of the widget left-aligned, set the alignment of the VStack to leading .

Now, we need this VStack to be placed inside another view with a background color of cyan, so let’s put the VStack in a HStack and add the background color as well. Also, a little bit of padding might look nice.

Here, we’ve used ContainerRelativeShape().fill() for the radius of the background. It’s important to note that according to widget-design guidelines, it’s not recommended to use fixed rounded corners — instead use CornerRelativeShape , which draws its radius concentric to its superview.

To make the HStack fully occupy the view, let’s add a Spacer(minLength: 0) along with the VStack in the HStack.

Now, we have almost achieved half of what needs to be built.

To build the last checked view, add a new VStack to add the two Text views.

Here, we’ve used the new Swift syntax for Text to display a date relative to the current time — for example, Text(“data.date, style: .relative”) . Using this with a widget ensures the time shown in the widget is relative to the current system time and is always updated every second.

Widget views aren’t meant to update every second, so iOS gives an option to place a dynamic time text. The per second updates are handled efficiently.

Now, finally, placing them in a ZStack to get the yellow background color and adding some padding for the VStack inside it, let’s extract the two subviews to WeightView and LastUpdatedView using Xcode.

Once that’s done, the final code for WidgetView would be:

And the created widgets would look like:

Preview for newly created widget

By now, you might have noticed we’ve designed the view for .systemSmall family, and the widget isn’t looking good for other sizes. To make the .systemMedium widget look more appealing, let’s add in a relevant icon which is shown only for .systemMedium widget.

First, create an environment variable with:

@Environment(\.widgetFamily) var widgetFamily

The value of this variable is set as .systemMedium if the user has placed a medium-size widget on his home screen. In that case, you can use:

if widgetFamily == .systemMedium {

//Render view here

}

You can add in the image to either side of the current content.

Making the ‘.systemMedium’ widget prettier

Exercise

Design the widget to look good for .systemLarge format, too. Currently, it looks like:

For now, we won’t be supporting the large size for widgets. To support only the desired widget-size families, head to the main function, and add .supportedFamilies([.systemSmall, .systemMedium]) to ensure users can’t place a .systemLarge widget on their home screen.

Adding ‘.supportedFamilies’ on line 9 to display specific size classes

Putting it all together

Now that we’ve designed the widget view, all that’s left is to add this widget view to the app’s widget extension. Remember the @main function we talked about earlier? (Scroll to the “Understanding the boilerplate” section above.) To get the WidgetView as a widget in your app:

Add WidgetView(data: .previewData) , as shown, replacing the default widget view, and run this on the simulator:

Widgets added to the home screen

And voila! You have your first widget added to your iOS application!

You can download the completed project here on GitHub.