How to Create Widgets in iOS 14
By: Bruno Rocha
Tags: Swift Programming, iOS, Xcode, Swift, Programming

TimelineProvider

The content displayed by iOS 14’s widgets works similarly to watchOS’s complications in the sense that instead of having an extension that is running all the time, you provide at once a “timeline” of events that the OS should display throughout the hours, days, or even weeks. This is useful for apps like Weather and Calendar where you can “predict” what is going to be displayed in the future, as you already have that information.

In our case, since we are unable to predict Swift’s commits, we’ll provide a timeline that contains only a single event — making iOS refresh our widget on a more regular basis.

To create a Timeline , we first need to define a TimelineEntry . A TimelineEntry only requires the Date when this entry is expected to be rendered in the widget, but it can also contain any additional information that you require. In our case, our entries will contain the Commit that we want to display in the widget:

But before creating the timeline, we need to be able to fetch such commits. Let’s create a CommitLoader class that fetches and parses Swift's latest commit:

When fetch is called, this loader type sends a request to GitHub's public API and parses the latest commit, giving us the message, author, and its timestamp. We can now create a Timeline that fetches the latest commit, adds it as an entry, and schedules itself to be updated after a while:

struct CommitTimeline: TimelineProvider {

typealias Entry = LastCommitEntry

/* protocol methods implemented below! */

}

The TimelineProvider protocol has two methods that we need to implement.

1. snapshot() - the fake information of the widget

The required snapshot() method of the TimelineProvider protocol defines how your widget should be configured when it appears in transient situations, such as the widget selection screen. This configuration will be used when showing correct information doesn't matter:

To create a snapshot configuration, all you have to do is create and return a fake entry of your TimelineEntry object:

timeline() - the true information of the widget

The timeline() method, however, defines the real information that should be used by your widget. The objective is for you to return a Timeline instance that contains all the entries you want to display when they are expected to be displayed (the date of the entry) and when the timeline "expires."

Because our app can’t “predict” its future state like a Weather app, creating a timeline with a single entry that should be displayed immediately will suffice. This can be done by setting the entry’s date to the current Date() :

The policy property of the timeline is what defines when iOS should attempt to discard this timeline and fetch a new one. Currently, they can be .never (widgets displaying a static content that never changes), .atEnd (when the last entry in the timeline is displayed), or .after(Date) (a specific amount of time after displaying the timeline for the first time). Since our timeline only has one entry, I decided to use .after to tell iOS that this widget should be reloaded every five minutes.

Note, however, that the widget API’s documentation states that you can’t predict when the widget will be updated. Even though the timeline itself will indeed be fetched again after five minutes, there’s no guarantee that the iOS will update the view at the same time. From my personal experience, the view actually took around 20 minutes to update by itself. The update time is based on a few factors, which include how often the widget is seen by the user. If you need to force a widget to update, you can use the WidgetCenter APIs from your main app to reload all timelines (or a specific one):

WidgetCenter.shared.reloadAllTimelines()