← All shorts
66iOS 14+2 min read

WidgetKit: Timelines and Reload Policies

WidgetKit renders a widget from a pre-built schedule of entries, not a continuously running process.

A widget's process isn't alive to redraw itself on a timer — WidgetKit renders from a schedule of future states you hand it up front, then the system decides when to actually swap the next one in.

#The new way

import WidgetKit
import SwiftUI

struct StepCountEntry: TimelineEntry {
    let date: Date
    let steps: Int
}

struct StepCountProvider: TimelineProvider {
    func placeholder(in context: Context) -> StepCountEntry {
        StepCountEntry(date: .now, steps: 0)
    }

    func getSnapshot(in context: Context, completion: @escaping (StepCountEntry) -> Void) {
        completion(StepCountEntry(date: .now, steps: 4200))
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<StepCountEntry>) -> Void) {
        let now = Date()
        let entries = (0..<4).map { hour in
            StepCountEntry(date: now.addingTimeInterval(Double(hour) * 3600), steps: 4200 + hour * 500)
        }
        completion(Timeline(entries: entries, policy: .atEnd))
    }
}

struct StepCountWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "StepCount", provider: StepCountProvider()) { entry in
            Text("\(entry.steps) steps")
        }
        .configurationDisplayName("Step Count")
        .description("Shows your step count throughout the day.")
    }
}

#Why it matters

  • The system, not your app, decides exactly when to render each entry — the widget process doesn't need to be running
  • A single getTimeline call can queue up hours of future states, so the UI updates smoothly without repeated wakeups
  • TimelineReloadPolicy (.atEnd, .after(date), .never) controls when WidgetKit asks your provider for a fresh timeline
  • Battery cost is bounded because rendering happens on a schedule you control, not continuously

#Gotcha

Reload budgets are real but not documented as a fixed number, and they're not guaranteed — WidgetKit throttles how often it'll actually call getTimeline again based on the widget's visibility and a system-wide budget, so an .atEnd policy on a short timeline can end up refreshing far less often than the entries suggest. Calling WidgetCenter.shared.reloadTimelines(ofKind:) from the app doesn't bypass this either; it's still subject to the same budget. Design timelines assuming refreshes will be less frequent than requested, and verify current budget behavior against Apple's docs rather than a fixed number, since it has changed across iOS releases.

widgetkitioswidgets

Related shorts