Widgets on iOS
By: Rakshith N
Tags: Ios Widgets, Swiftui, Swift, Ios 14

Apple recently got on board with supporting widgets for iOS. They provide minimal but yet useful information to the user, without accessing the application.

This article aims at introducing this new world of Widgets. We will explore the WidgetKit SDK extensively and understand the components and procedure of building widgets.

You would need to be familiar with SwiftUI as building widgets makes extensive use of SwiftUI. As a widget is not an application in its true nature, it does not make use of app delegates or navigation stacks. Moreover a widget exists only with a parent application, it is not a standalone entity.

To summarise, widgets provide the user with a snapshot of the application information. The OS triggers the widget at set times to refresh its view, more on this in just a while.

Requirements

First, to start working with widgets, you would need the following:

Mac OS 10.15.5 or later. Xcode 12 and above, link to xcode 12, (this in case the update from app store does not work, mainly due to lack of space on the disk)

The Setup

As previously mentioned, a widget cannot exist without a parent application. So first create a single view application.

For the life cycle option I am selecting SwiftUI lifecycle, this would use the new convention of the @main attribute to determine the starting point in the code.

Once done, we now need to add a new widget extension which will house the code for our widget.

Select File -> New -> Target -> Widget extension.

Give the widget any name you want, make sure you uncheck the option ‘Include Configuration Intent’. I will get to explaining this in just a while.

Next, click Finish and you would see a pop up asking you to activate your widget extension scheme, click on activate, and viola, your setup is complete.

Now, select the swift file under the widget extension, you would see that Xcode has already generated most of the skeleton code. Let us understand each of these parts before we go ahead.

Navigate to the struct of type Widget, it would be the name of the widget file you entered during setup. You will notice the ‘@main’ attribute for this struct, indicating that this is the starting point for your widget. Let us break down the different properties here.

Kind : this is an identifier for the widget, which can be used for performing updates or making queries.

Provider : this value is of type ‘TimelineProvider’, which is the data source for the widget. It is responsible for determining what data needs to be displayed on the widget at different points of time.

Content : This is the SwiftUI view which will be displayed to the user.

Notice that under the WidgetConfiguration we are returning a StaticConfiguration instance. There exist two types of Widget configurations, static and intent. The intent configuration allows the user to configure the widget at run time. In the current configuration, the data displayed is static, that is, the user cannot change the data which is displayed on the widget during run time.

Moving on, let us talk about the ‘SimpleEntry’ struct. This is of type ‘TimelineEntry’. It forms the data model for the widget. In our example we have only a date parameter, you could add other values based on your requirement. If for example you want to display a text to the user based on certain conditions, you would add the text parameter in here. Your struct needs to implement the date requirement as this provides the OS with different timestamps of your data.

Next up is the content of the widget, the struct ‘Static_WidgetEntryView’ is where you can let your creativity soar and design your widget. Keep in mind that there are certain restrictions on the size of the widget.

Supporting different sizes for the widget

WidgetKit supports three sizes, namely, small, medium and large.

Use the ‘supportedFamilies’ option during initiation of your widget to determine which sizes you would want to support, by default all sizes are enabled.

supportedFamilies([.systemSmall, .systemMedium, .systemLarge])

Given that users can choose from three sizes for the widget, we would need to incorporate the UI for the widget likewise, to have the best look and feel for each size. In our View file, we need to be able to determine which size family has been selected by the user in order to change the UI in line with it. For this, widget kit provides an environment variable of the family size selected. We could then set up the UI based on the selected size.

The TimeLine Provider

The last bit comprising our building blocks is the Provider. The struct Provider is of type ‘TimelineProvider’, which implements three methods

One to provide a placeholder for the widget, second to provide the snapshot and third to return the current timeline.

The snapshot is used by the OS when it is required to return a view as quickly as possible, without loading any data or making network calls. It is used in the Widget gallery, which lets the user preview the widget before adding it to the home screen. Ideally for a snapshot, configure a mocked view of the widget.

The getTimeline function, this method configures what the widget needs to display at different points in time. The Timeline is basically an array of objects that conform to the TimelineEntry protocol. For example, if you decide to create a widget to countdown the days left to a particular event. You would need to create some views starting from the current date up until the deadline.

source: wwdc video

This is also where you would make any async network calls. The widget can make network calls to fetch data or it could use a container shared from the main host application to procure the data. The widget will display the data once the completion is called.

Timeline Reload Policy

In order to decide when the OS needs to update to the next set of views, it uses the ‘TimelineReloadPolicy’.

The ‘.atEnd’ reload policy specifies that the OS will reload the timeline entries once there are no more entries. You will notice I have created a timeline separated by a duration of one minute. Five entries of the view are added, this way the widget will update after each minute and show the time accordingly. Once the duration of 5 minutes has elapsed, the ‘getTimeline’ method is invoked to retrieve the next set of views.

The TimelineReloadPolicy also provides options such as ‘after(date)’ and ‘never’, to update the timeline after a said date and to never update the timeline respectively.

Run the project, on the home screen long press and click the ‘+’ on the top left. From the list of options select your widget app, you can then select the widget style you wish to add and click on ‘Add Widget’. You should see the widget displaying the current time.

Dynamic Widget Configuration

So far our widget has been more or less static, the user cannot interact with the widget or define what the widget displays during runtime. Using the Intent configuration, we would be able to make our widget dynamic.

Initially when we setup the project we unchecked the option of ‘Include Configuration Intent’ to make the widget customisable, let us now see how we can make our widget more interactive.

For the purpose of this demonstration, we will setup our widget to let the user select from a list of options, in this case a list of cities.

Setup for a custom intent

1) We need to create a custom intent definition, we will make use of ‘SiriKit Intent Definition’ for this. Click on the File menu option, select the New File option and proceed to select ‘SiriKit Intent Definition’, give it a name, I am naming it ‘CityNamesIntent’.