Today Extension (Widget) Development for iOS
Today Extension is the old widget mechanism for Notification Center, available since iOS 8. Since iOS 14, Apple introduced WidgetKit—a new API for widgets on the home screen and lock screen. Today Extension technically still works, but Apple is clearly guiding developers toward WidgetKit. If the goal is a home screen widget, use WidgetKit. If backward compatibility with iOS 13 is needed—use Today Extension.
WidgetKit: How It Works Internally
WidgetKit is not a View that updates in real time. It's snapshots—pre-rendered states shown on a schedule.
The application provides a TimelineProvider that returns an array of TimelineEntry—each with a display date and data. The system renders the SwiftUI View for each entry in advance and switches between them over time. There's no live code in the widget—only static snapshots.
This breaks conventional thinking. You cannot create a countdown timer through Timer—you must use Text(..<deadline>, style: .timer), which the system updates natively. You cannot update the widget via push notification without the WidgetKit API.
Widget update. WidgetCenter.shared.reloadTimelines(ofKind:) is called from the main application or Background App Refresh. However, the system allocates limited budget for background updates (roughly 40-70 times per day). If TimelineProvider.getTimeline() requests updates too frequently through TimelineReloadPolicy.after(Date)—the system simply ignores this and updates less often.
AppIntents and Interactive Widgets (iOS 17)
Since iOS 17, widgets support interactivity through AppIntents. Buttons and Toggle directly in the widget execute AppIntent.perform() without opening the application.
Implementation: a structure conforming to the AppIntent protocol, decorated with @MainActor. In the widget's SwiftUI: Button(intent: MyIntent()) or Toggle(isOn: binding, intent: MyToggleIntent()).
Constraints: you cannot show Alert or Sheet in the widget. Only simple actions. For complex interaction—deep link to the main application through .widgetURL() or Link().
App Group and Widget Data
The widget is a separate Extension process. Main application data is not directly accessible. The only path: App Group.
UserDefaults(suiteName: "group.com.yourapp") for small data (flags, numbers, short strings). FileManager.containerURL(forSecurityApplicationGroupIdentifier:) for files, JSON, images.
Common mistake: storing widget images in the main application's normal Documents. The widget doesn't see that path. All resources for the widget—only in App Group container.
Sizes and Lock Screen
WidgetKit supports: systemSmall (2×2), systemMedium (4×2), systemLarge (4×4), systemExtraLarge (iPad, 8×2). iOS 16+: accessoryCircular, accessoryRectangular, accessoryInline for lock screen and Apple Watch face.
A lock screen widget (accessoryCircular) always renders in grayscale in watch ambient mode. Colors work only on the iPhone lock screen. Design should look good in both.
Today Extension (Legacy)
NCWidgetProviding.widgetPerformUpdate(completionHandler:) is the update point. The Extension must call completionHandler(.newData) or .noData within 30 seconds. If not called—the system considers the Extension hung.
Size: compact (fixed) and expanded through preferredContentSize. UIKit, no SwiftUI.
Timeline
WidgetKit widget (one size, static data): 2–3 weeks. Widget with interactivity (iOS 17 AppIntents), multiple sizes, lock screen: 4–7 weeks. Today Extension legacy: 2–4 weeks. Cost is calculated after analyzing data and update frequency.







