How I Built a Spaced-Repetition Widget With SwiftUI and WidgetKit

Wait 5 sec.

A “word of the day” widget is easy to describe: choose a word, render it, update tomorrow.That was not enough for Slovo, a one-minute Russian learning app, and its central product idea is that a glance can become a recall attempt. If the widget only showed today’s word, it would stop being useful after the learner recognized it. I wanted it to mix new exposure with spaced review.The final implementation looks simple on the Home Screen. Getting there required decisions about data sharing, timeline generation, rendering modes, fallback behavior, and extension packaging.The Data Boundary: App vs. Widget ExtensionAn iOS widget is not a little view running inside the main app. It is a separate extension with its own process and lifecycle. That means the main AppModel cannot hand an in-memory review queue directly to the widget.Slovo uses an App Group-backed UserDefaults suite as the small bridge between them.Whenever progress changes, the app stores two pieces of widget state:UserDefaults.shared.set(widgetWord.id, forKey: "widgetWordID")UserDefaults.shared.set( reviewQueue(limit: 5).map(\.id), forKey: "widgetRotationIDs")WidgetCenter.shared.reloadAllTimelines()This is intentionally a narrow interface. The widget does not need the entire learning database. It needs the preferred current word and a small ordered list of review candidates.That kept the extension lightweight and avoided duplicating scheduling logic in two targets.Building a 24-Hour TimelineWidgetKit asks a provider for timeline entries. Slovo creates one entry per hour for the next 24 hours.The sequence begins with the daily word and adds up to five review words. Each later slot chooses from that cycle:let cycle = [slotDaily] + rotation.filter { $0.id != slotDaily.id }let word = slot == 0 ? (preferredWord ?? slotDaily) : cycle[slot % cycle.count]There are two details here that prevented subtle bugs:The daily word is filtered out of the review rotation so the same item is not duplicated.Slot zero prefers the word recently selected by the app, allowing a widget refresh to reflect the learner’s current context immediately.If a new user has no review queue, the provider uses words from recent days as fallback material. An empty state would waste the widget’s most important moment: the first day someone adds it.One View, Five Widget FamiliesThe widget supports:.systemSmall.systemMedium.accessoryInline.accessoryCircular.accessoryRectangularThe Lock Screen families cannot simply shrink the Home Screen card. Each has a different information budget.The inline version combines the stressed word and meaning. The circular version shows an icon and first letter. The rectangular version has enough room for the stressed word, phonetic guide, and meaning.The Home Screen sizes preserve Slovo’s editorial hierarchy: level and state at the top, a large serif Russian word, then meaning and pronunciation.The Tinted-Widget ProblemThe first full-color design used fixed ink, paper, and accent colors. That looked correct in normal rendering and nearly disappeared when iOS applied a tinted Home Screen style.Tinted widgets do not preserve arbitrary colors. The system maps luminance and accentable content into the user’s chosen appearance. A muted beige background plus dark editorial text can turn into a low-contrast block.Slovo checks widgetRenderingMode and changes its palette:let fullColor = renderingMode == .fullColorlet accent = fullColor ? Color(hex: entry.word.colorHex) : .whitelet ink = fullColor ? Color.slovoInk : .whitelet muted = fullColor ? Color.slovoMuted : .white.opacity(0.75)Accent pills and symbols use .widgetAccentable(), while the typography falls back to white with deliberate opacity differences.The lesson was broader than WidgetKit: supporting system customization means designing a hierarchy that survives after your brand colors are removed.Adding a Live Activity Without Making It a Second AppSlovo also has a focused “Russian Sprint” review. ActivityKit exposes its progress on the Lock Screen and Dynamic Island.The activity state is small:completed word count;total word count;current word; andwhether the sprint is finished.The expanded Dynamic Island shows the current Russian word and progress bar. Compact presentations reduce this to a book symbol and completion count. Tapping it deep-links to slovo://review.That keeps the Live Activity useful instead of decorative. Its job is to help a learner resume a short task.The Widget That Existed but Did Not ExistMy most time-consuming widget problem was not in the timeline code.The extension compiled. Xcode knew about the target. The widget worked in one development context. Then an installed build had no Slovo widget in the gallery.The real checklist crossed several layers:Is the widget extension included in the app’s Embed App Extensions phase?Do the app and widget share the same App Group entitlement?Does the archive contain the .appex bundle?Are signing profiles valid for both targets?Does the final IPA include the extension, not only the Xcode project?Was the old app fully removed before testing a newly packaged extension?This is an important distinction in iOS development: a feature can be correct in source code and absent from the shipped artifact.I now inspect the archive/IPA and test the actual TestFlight build instead of treating a successful local run as proof.What I Would Do DifferentlyIf I rebuilt this feature, I would establish an extension acceptance test before styling anything:Build the simplest widget target.Archive the app.Confirm the .appex is embedded.Install that artifact on a clean device.Verify App Group reads and timeline reloads.Only then add families, rotation, and visual polish.The algorithm was not the risky part. Distribution was.Slovo’s widget is now one of the product’s clearest differentiators because it connects learning science to an ordinary behavior: looking at your phone. But it only became real when I stopped asking “does the SwiftUI view work?” and started asking “does the exact build a user installs contain the whole system?”