RORK LABJP
MAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode requiredAPPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProEXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English descriptionFUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile developmentPRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developersWWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code appsMAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode requiredAPPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision ProEXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English descriptionFUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile developmentPRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developersWWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code apps
Articles/App Dev
App Dev/2026-06-14Advanced

Designing WidgetKit Timelines Around the Refresh Budget: Why My Wallpaper Widgets Stopped

Why does a home screen widget stop updating after the evening? A clear look at WidgetKit timeline design through three lenses: the refresh budget, the reload policy, and entry density. With a working TimelineProvider, an entry design that does not burn through the budget, and relevance-based prioritization, drawn from running six wallpaper apps solo.

WidgetKit5TimelineProvideriOS77Rork Max147WidgetsPerformance15

Premium Article

Running six wallpaper apps solo as an indie developer, I started getting more reports that "the home screen widget's photo of the day stays yesterday's after the evening." It switched correctly in the simulator, but on real devices it froze by evening. The thing creating that gap was an invisible mechanism: WidgetKit's refresh budget.

Unlike an app, a widget cannot redraw itself whenever and as often as it likes. The system assigns a rough daily budget for the number of refreshes, and once you spend it, no updates arrive until the next day. Build a timeline without knowing this and you get exactly this symptom — the budget is eaten in the morning, and the afternoon goes silent.

A timeline is a "schedule of the future"

The center of WidgetKit's model is that a widget does not get asked "what do you show right now" every time; instead it submits "the display schedule for a while ahead" all at once. The Timeline returned by a TimelineProvider is a schedule of several TimelineEntry values, each stamped with a time.

The system follows that schedule and switches to the next entry automatically when its time arrives. In other words, if you compute future displays ahead of time and hand them over in a batch, no queries to the system (no budget spend) happen in between. Grasp this and the design direction is set.

Emitting one entry at a time drains the budget

The first thing I did wrong was return a single entry per hour and repeat with .atEnd, meaning "come back when this one is done." It appears to work, but because every reload spends budget, on an eventful day the budget runs out in the early afternoon.

The right approach is to return several future entries in a single timeline generation. Compute, say, a full day of switches as 24 entries up front, pack them into one Timeline, and you coast through the day with almost no extra queries.

import WidgetKit
import SwiftUI
 
struct WallpaperProvider: TimelineProvider {
    func placeholder(in context: Context) -> WallpaperEntry {
        WallpaperEntry(date: Date(), imageName: "placeholder")
    }
 
    func getSnapshot(in context: Context, completion: @escaping (WallpaperEntry) -> Void) {
        completion(WallpaperEntry(date: Date(), imageName: todaysImageName()))
    }
 
    func getTimeline(in context: Context, completion: @escaping (Timeline<WallpaperEntry>) -> Void) {
        var entries: [WallpaperEntry] = []
        let calendar = Calendar.current
        let now = Date()
 
        // Generate 12 future entries at 2-hour steps and return them batched
        for hourOffset in stride(from: 0, to: 24, by: 2) {
            guard let entryDate = calendar.date(byAdding: .hour, value: hourOffset, to: now) else { continue }
            let name = imageName(for: entryDate)
            entries.append(WallpaperEntry(date: entryDate, imageName: name))
        }
 
        // Come back for the next timeline only once, at the start of tomorrow
        let tomorrow = calendar.date(byAdding: .day, value: 1, to: now)!
        completion(Timeline(entries: entries, policy: .after(tomorrow)))
    }
}

The crux of this is the for loop that builds the future entries in a batch, and the policy set to .after(tomorrow), declaring "the next pickup can wait until tomorrow." That dramatically cut the daily query count and the evening silence disappeared.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Take home a working Swift TimelineProvider that batches future entries instead of emitting one at a time, so you do not exhaust the daily refresh budget
Confirm in a table which of .atEnd, .after and .never to pick for which kind of widget
Understand the cause and fix for two symptoms I actually hit as an indie developer: updates dying in the evening, and a stale image after a tap
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-05-30
Killing Thumbnail Scroll Jank in an iOS Wallpaper App — Notes on ImageIO Downsampling
Lay out a lot of thumbnails and scrolling stutters while memory balloons. The culprit was full-size image decoding on the main thread. Here is how ImageIO downsampling and prefetching cut measured memory dramatically.
App Dev2026-05-26
Bolting WidgetKit onto a Rork iOS App: Implementation Notes from Rolling It Out to Six Wallpaper Apps Simultaneously
A hands-on note on adding a WidgetKit Extension to a Rork-generated iOS app, with operational lessons from rolling out widgets to six wallpaper apps at once — App Group plumbing, Timeline Provider choices, memory ceilings, and ASO side effects.
Dev Tools2026-04-05
Rork Max × WidgetKit & Live Activities Complete Guide 2026 — Dynamic Island Implementation
A complete guide to implementing iOS Widgets, Lock Screen widgets, and Dynamic Island Live Activities with Rork Max. Covers WidgetKit fundamentals, Timeline update strategies, App Intents integration, and monetization.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →