◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-05-26Advanced

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.

Rork585WidgetKit11iOS116SwiftUI66App Group4Wallpaper3AppIntent

✦ Premium Article

I'm Masaki Hirokawa, building iOS apps as a solo developer since 2014. I recently bolted WidgetKit-based widgets onto a set of wallpaper apps whose foundations were originally generated by Rork, and rolled them out to my existing lineup that has now passed 50 million cumulative downloads. Rork ships a React Native foundation quickly, but a Widget Extension cannot host a JavaScript runtime — so adding one after the fact means designing a small, self-contained native world that sits on top of the codebase Rork gave you.

These are the rollout notes from doing that across six wallpaper apps at once. I'll focus on the parts that tend to bite when widgets are added retroactively: Timeline Provider choices, App Group disk I/O, and the strict 30 MB memory ceiling for widgets.

Why I added widgets to Rork-based apps after the fact

The trigger was a small cluster of App Store reviews asking to "see today's wallpaper without opening the app." Until then, all wallpaper data lived in the Rork-generated React Native UI, so adding widgets meant drawing a new boundary between the JS layer and the native layer.

A second reason was ASO. App Store listings display a "Widgets" badge in screenshots once an app ships a widget, and searches for widget-related terms become more likely to surface the listing. In the two weeks after Beautiful HD Wallpapers shipped its widget update, impressions for the query "wallpaper widget" rose about 11% versus the prior two weeks. The absolute volume is modest, but when each app takes only about a day to a day and a half to retrofit, the math works out.

Operationally, widgets also let users "browse a little" without launching the app. That improved how often people came back to the wallpaper detail screen, which is harder to measure in numbers but visible in retention. Of the six apps, only the home-screen widget kept consistent weekly use; lock-screen widgets spiked briefly on release and then settled into a much smaller share of impressions.

App Group and shared storage — bridging Rork's JS layer to a native widget

A widget extension cannot execute JavaScript, so whatever the Rork-generated app stores via AsyncStorage or React state is invisible to the widget by default. The bridge is an App Group: a shared UserDefaults suite plus a shared container directory on disk.

In my setup, the main app exposes a thin native module that writes to the App Group from React Native via a bridge. The widget reads the same App Group suite directly.

// AppGroup.swift — included in BOTH the main app target and the widget extension
import Foundation
 
enum AppGroup {
    static let identifier = "group.net.dolice.wallpapers.shared"
 
    static var defaults: UserDefaults {
        guard let d = UserDefaults(suiteName: identifier) else {
            fatalError("App Group suite missing. Check Capabilities.")
        }
        return d
    }
 
    static var containerURL: URL {
        guard let url = FileManager.default.containerURL(
            forSecurityApplicationGroupIdentifier: identifier
        ) else {
            fatalError("App Group container unavailable. Check provisioning.")
        }
        return url
    }
}

Two pitfalls are worth flagging. First, the App Group identifier has to be enabled on the Signing & Capabilities tab of both the main app and the widget extension. Forgetting this on the widget target produces a release build whose widget is silently blank; the simulator can mask it, so I always re-verify on a TestFlight build.

Second, keep filenames in the shared container ASCII and short. I observed several intermittent failures on devices in low-power mode when filenames included localized characters or were unusually long. Today I store thumbnails as wallpaper-thumb.jpg, and any metadata goes into UserDefaults via Codable.

✦

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
✦A minimum-viable recipe for adding a WidgetKit Extension to a Rork-generated React Native iOS project, without touching the JS layer
✦How I chose between three Timeline Provider patterns (static, timer-based, app-driven) when rolling widgets out to six wallpaper apps in parallel
✦Operational lessons on AppIntent-based configuration, App Group disk I/O, the 30 MB widget memory ceiling, and Lock Screen widget tradeoffs
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-04-26
Native iOS Apps with Rork: SwiftUI to TestFlight
New iPhone projects in Rork are generated in Swift and SwiftUI, and Rork Pro carries them all the way to the App Store. How to tell an old Expo project from a new one, costs in credits, the skeleton prompt, device testing, when to drop into Xcode, and TestFlight submission.
⬡ Dev Tools2026-05-10
Why Your Rork iOS Widget Keeps Showing Stale Data — Rebuilding the App Group + WidgetKit Pipeline That Actually Refreshes
Your Rork-built iOS widget shows yesterday's data. Rebuild the App Group, shared UserDefaults, and timeline reload pipeline so parent and widget share state.
⬡ Dev Tools2026-09-05
Your Widget Extension Can Submit a BGTaskScheduler Request. It Just Cannot Register the Handler
Calling BGTaskScheduler from a widget extension compiles, submits, and returns true — and then nothing runs. Here is why registration belongs to the host app only, and how I moved refresh ownership back where it belongs across my wallpaper apps.