Generating more dynamic iOS Widget timelines

When you start your first widget for iPhone / iPad, the Xcode template project it will give you a getTimeline example (or at least it would pre-iOS 16) that would generate a specific number of widget snapshots.

Many people, including myself, would then ask why does it feel like the widget never updates? Part of the reason when you say, as an example, generate 5 widget snapshots they are generated right then and there and stored for later use. So, it’s really only beneficial if your data should not change in the timeframe those 5 snapshots cover.

This tweet has a good set of replies by Curtis Herbert which explains the two ways you might want to generate iOS widget timelines.

The original tweet question will show you a good example of what you want for more dynamic data (the left image) and more static data (the right one).

In case the tweets disappear, here’s a copy of the left image and how you might want to generate a widget timeline for more dynamic data by attempting to generate a new snapshot every hour.

// When it requests data
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) {
    var entries: [Entry] = []

    // Just one Entry, gets the data now and again after an hour
    let now = Date.now
    let entry = Entry (date: now, widget: .demoWidget)
    entries.append (entry)

    // Go again in 1 hour
    let calendar = Calendar.current
    let hourFromNow = calendar.date (byAdding: •hour, value: 1, to: now) !
    let timeline = Timeline(entries: entries, policy: .after (hourFromNow))
    completion (timeline)
}

Now, keep in mind this isn’t necessarily a perfect solution either. This will require work on behalf of your app more often. You’re not guaranteed that the system allows that to happen, and you might find that you need a middle of the road approach between the two examples.

A middle of the road approach may include the following. First, following the static snapshot generation example and generate 5 snapshots. They might all look the same, but will be guaranteed to be used. If your data does not change, well, then that’s fine that they look all the same.

However, when an important change to the data does happen, and you want the widget to be refreshed, then you could call WidgetCentre to refresh the timelines.

import WidgetKit

func saveMyModel() {
    // Make important data changes and save...
    WidgetCenter.shared.reloadAllTimelines()
}

By doing this, you get the efficiency of storing a set of pre-generated snapshots while being able to refresh the data when it matters.

A word of caution, when you are pre-generating your widget timeline then make sure you are not generating too many entries. There’s a limit and if there is a chance you are going to be throwing data away and regenerating it then it might make sense to keep the potential waste low.

I believe this is what the fine app Reeder does. If you use it, you’ll notice that if you read all your new RSS feed entries in the app, as soon as you exit the app, the widget is updated to reflect you have no new entries available.