Irbisa · Cheatsheet
App Extensions & WidgetKit
WidgetKit timelines, Live Activities, App Intents, share and notification extensions, App Groups
What is an app extension, which kinds are common, and how does it differ from the containing app in process, lifecycle and memory?
What are the building blocks of a WidgetKit widget, and what actually runs where?
struct StepsEntry: TimelineEntry { let date: Date let steps: Int} struct StepsProvider: TimelineProvider {…What do
placeholder,snapshotandtimelinedo in a widget's timeline provider, and when does WidgetKit call each?struct SalesProvider: AppIntentTimelineProvider { // Synchronous and instant: the redacted skeleton. func placeholder(in context: Context) -> SalesEntry { SalesEntry(date: .now, total: 1234, store: .sample) }…How do you deep-link from a widget into a specific screen, and what is the difference between
widgetURLandLink?struct OrdersWidgetView: View { let entry: OrdersEntry @Environment(\.widgetFamily) private var family var body: some View { VStack(alignment: .leading) {…What is a Live Activity, and how does it differ from a widget and from a push notification?
This widget view should count down every second. Why does this countdown never tick on the Home Screen, and how do you fix it?
struct CountdownView: View { let entry: CountdownEntry @State private var secondsLeft = 0 var body: some View { Text("\(secondsLeft) s left") .onAppear { secondsLeft = Int(entry.deadline.timeIntervalSinceNow) } .onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()) { _ in secondsLeft = Int(entry.deadline.timeIntervalSinceNow) } }}How do the reload policies
.atEnd,.after(_:)and.neverwork, and how does the app refresh a widget right after its data changes?func timeline(for configuration: SalesIntent, in context: Context) async -> Timeline<SalesEntry> { let hours = await SalesStore.forecast(for: configuration.store) let entries = hours.map { SalesEntry(date: $0.start, total: $0.total) } // Ask again only when the numbers can actually differ. return Timeline(entries: entries, policy: .after(SalesStore.nextOpeningBell))…Find the bug: the steps widget always shows 0, although the app shows the right count and reloads the widget after every save.
// Compiled into both the app and the widget extension.enum StepsStore { private static var defaults: UserDefaults { .standard } static func save(_ steps: Int) { defaults.set(steps, forKey: "todaySteps") } static func todaySteps() -> Int { defaults.integer(forKey: "todaySteps") }} // App: called when HealthKit delivers new samples.func didUpdateSteps(_ steps: Int) { StepsStore.save(steps) WidgetCenter.shared.reloadTimelines(ofKind: "StepsWidget")} func makeEntry() -> StepsEntry { // widget extension, in getTimeline StepsEntry(date: .now, steps: StepsStore.todaySteps())}This provider should show each of the next four hourly forecasts as its hour arrives. What happens instead, and what is the fix?
func timeline(for configuration: CityIntent, in context: Context) async -> Timeline<WeatherEntry> { let forecast = await WeatherCache.hourly(for: configuration.city) let entries = forecast.prefix(4).map { hour in WeatherEntry(date: .now, temperature: hour.temperature) } return Timeline(entries: entries, policy: .atEnd)}How often can a widget refresh, and what counts against its refresh budget?
How do you build a user-configurable widget with App Intents, and where does the list of choices come from?
struct ProjectEntity: AppEntity { let id: String let name: String static let typeDisplayRepresentation: TypeDisplayRepresentation = "Project" var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(name)") }…How do interactive widgets work since iOS 17, and why does an
onTapGesturein a widget do nothing?struct CompleteTaskIntent: AppIntent { static let title: LocalizedStringResource = "Complete task" @Parameter(title: "Task") var taskID: String init() {}…What are widget rendering modes, and why do a widget's colours disappear on the Lock Screen or a tinted Home Screen?
struct BuildStatusView: View { @Environment(\.widgetRenderingMode) private var renderingMode let build: Build var body: some View { VStack(alignment: .leading, spacing: 6) {…In ActivityKit, what belongs in
ActivityAttributesversus itsContentState, and how do you start, update and end a Live Activity?struct DeliveryAttributes: ActivityAttributes { // Fixed at request time. let orderID: String let restaurantName: String // Replaced on every update; at most 4 KB.…What presentations does a Live Activity have in the Dynamic Island, and when does the system use each one?
struct DeliveryLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: DeliveryAttributes.self) { context in // Lock Screen, devices without an Island, and update banners. LockScreenView(context: context) } dynamicIsland: { context in…What is an App Intent, and what do you add so an action works from Siri and Spotlight without the user setting up a shortcut?
struct LogDrinkIntent: AppIntent { static let title: LocalizedStringResource = "Log a drink" @Parameter(title: "Drink", requestValueDialog: "Which drink?") var drink: DrinkEntity…Find the bug: text-only pushes handled by this notification service extension arrive about 30 seconds late; pushes with an image arrive at once.
final class NotificationService: UNNotificationServiceExtension { var handler: ((UNNotificationContent) -> Void)? var best: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { handler = contentHandler best = request.content.mutableCopy() as? UNMutableNotificationContent guard let best, let link = best.userInfo["image"] as? String, let url = URL(string: link) else { return } Task { if let attachment = try? await ImageAttachment.download(url) { best.attachments = [attachment] } contentHandler(best) } } override func serviceExtensionTimeWillExpire() { if let handler, let best { handler(best) } }}How does a share extension receive a shared file, and how do you upload a large one reliably?
final class ShareViewController: UIViewController { static let groupID = "group.com.example.clips" override func viewDidLoad() { super.viewDidLoad() guard let item = extensionContext?.inputItems.first as? NSExtensionItem, let provider = item.attachments?.first(where: {…How do you start and update a Live Activity from your server with push notifications, and which tokens does the backend need?
func observeLiveActivityTokens() { // 1. One token per activity type, before anything runs. Task { for await token in Activity<DeliveryAttributes>.pushToStartTokenUpdates { await api.register(startToken: token.hex) }…A widget renders blank on a device while the app works fine, and your
printoutput never appears. How do you debug the widget extension?