WWDC Quick Look đź’“ By SwiftGGTeam
WidgetKit foundations

WidgetKit foundations

Watch original video

Highlight

WidgetKit uses a timeline mechanism to let widgets render on demand in a separate process. Developers provide data entries and SwiftUI views, while the system shows the right content at the right time and uses reload policies to balance freshness with battery life.

Core Ideas

Why widgets need a separate process

Your app might be written in SwiftUI or UIKit, but widgets are always built with SwiftUI. A widget runs in its own Widget Extension process, not inside the main app.

That means the main app and the widget cannot directly share memory. Data must flow through a shared container in an App Group, such as shared UserDefaults or a shared database. (02:30)

The cost of this design is an extra layer of data synchronization. The benefit is that the system can render your widget at any time without waking the main app. The widget’s view is archived and cached by the system, then displayed directly when its scheduled time arrives, without keeping your code running.

Three standards for a good widget

Apple summarizes memorable widgets in three words: (01:03)

  • Glanceable: Users can get the information with a quick glance. A weather widget shows only the current temperature and conditions, without requiring expansion.
  • Relevant: Content changes with time, place, and user habits. A calendar widget shows today’s meetings in the morning and switches to evening plans later in the day.
  • Personalizable: Users can configure what appears. A Photos widget lets users choose a specific album and show only photos from a family camping trip.

These three standards determine whether users keep your widget on their Home Screen for the long term.

Timeline: the heart of a widget

WidgetKit does not ask your extension for “the current view.” Instead, it asks for view data across a set of future moments. That is the timeline. (03:12)

Each timeline is made up of multiple Timeline Entries. Every entry carries the data for one specific point in time. The system renders and archives those views ahead of time, then displays them directly when the time arrives.

A Timeline Provider needs to handle three states: (05:26)

  • Snapshot: The preview image in the widget gallery. This is your first impression, so use realistic and appealing data. For example, a reading app can default to the cover and progress of a popular book.
  • Placeholder: The placeholder view shown when the widget first loads. It must return synchronously and cannot read from disk or call the network. SwiftUI’s .redacted() modifier can quickly create a simplified view.
  • Timeline: The actual sequence of timeline entries. Each entry contains all the data needed to render a specific moment.

Refresh strategy: three reload policies

Timeline refresh behavior is controlled by a reload policy. The three options map to three different scenarios: (07:55)

  • atEnd: Refresh automatically after the timeline runs out of entries. This fits content whose sequence is not fixed and needs continuous replenishment, such as reading reminders throughout the day.
  • afterDate: Refresh at a specific time. This fits content with a clear refresh moment, such as a reading schedule widget that recomputes upcoming plans at midnight.
  • never: Do not refresh automatically. This fits content that only needs updating after user interaction or app state changes, such as a reading progress widget. Trigger updates explicitly with the WidgetCenter reload API or push notifications when needed.

WidgetKit gives each widget a refresh budget, and that budget is influenced by how often users view it. Frequent foreground refreshes may be throttled. The last reload call before the app enters the background is often the best timing. (10:27)

If content has clear start and end times, needs high-frequency updates, and requires push alerts, such as a sports game, consider a Live Activity instead of a widget.

Three ways widgets connect to apps

A widget is not an island. Three mechanisms bind it back to the main app: (13:25)

  • Deep Link: Tapping the widget opens a specific screen in the app. A reading widget can jump to the current book’s detail page instead of the app’s home screen.
  • Configurable widget: Users choose what the widget displays. A weather widget lets users choose a location; a reading widget lets users choose which book to track.
  • Interactive controls: Buttons and toggles let users act directly from the widget. After finishing a chapter, the user can tap a button on the widget to check in without opening the app.

Interactive controls are implemented through App Intents. Widget views are archived and rendered by the system, so your code is not executing at runtime. Buttons and toggles are therefore bound to App Intents, which the system performs on behalf of the user during interaction. (16:45)

Adapting to Tinted and Clear modes

iOS supports two system customization modes: Tinted and Clear. In these modes, the system replaces the widget background with a glass material and applies tint treatment to content. (17:23)

SwiftUI handles most cases well, but images can break. For example, a book cover can turn into a white square in Clear mode because the system cannot tint that image correctly.

The solution is the .widgetAccentedRenderingMode(.fullColor) modifier, which tells the system to keep that region in its original colors. (18:17)

Details

Create your first widget

(03:50)

When Xcode creates a Widget Extension, it generates the basic code automatically. The simplest widget needs three pieces: a Widget struct, a Timeline Provider, and a SwiftUI view.

struct DailyReadingGoalWidget: Widget {
    let kind = "DailyReadingGoalWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: kind,
            provider: DailyReadingGoalProvider()
        ) { entry in
            DailyReadingGoalView(book: entry.book,
                                 message: entry.message,
                                 timeOfDay: entry.timeOfDay)
            .environment(\.colorScheme, .dark)
            .containerBackground(for: .widget) {
                Background()
            }
        }
    }
}

Key points:

  • kind is the widget’s unique identifier. The system uses it to distinguish different widget types.
  • StaticConfiguration is for non-configurable widgets. If users need to configure parameters, use AppIntentConfiguration instead.
  • provider generates Timeline Entries and is the core data layer.
  • The closure parameter entry is data for a single point in time. The returned SwiftUI view is archived by the system.
  • .containerBackground(for: .widget) declares the widget’s background view. This is the key to adapting to Tinted and Clear modes: the system needs to know which View is the background before it can replace it with a glass material.
  • .environment(\.colorScheme, .dark) forces dark mode to keep the widget readable on a light Home Screen.

Limit the supported widget sizes

(12:25)

Widgets support multiple sizes, or families, including systemSmall, systemMedium, systemLarge, and the new iOS 27 systemExtraLargePortrait. Supporting as many sizes as possible is recommended, but not every widget fits every size.

struct DailyReadingGoalWidget: Widget {
    let kind = "DailyReadingGoalWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: kind,
            provider: DailyReadingGoalProvider()
        ) { entry in
            DailyReadingGoalView(book: entry.book,
                                 message: entry.message,
                                 timeOfDay: entry.timeOfDay)
            .environment(\.colorScheme, .dark)
            .containerBackground(for: .widget) {
                Background()
            }
        }
        .supportedFamilies([.systemMedium])
    }
}

Key points:

  • .supportedFamilies limits the widget sizes that are available. This example only supports systemMedium.
  • The same widget can reuse one Timeline Provider and provide different SwiftUI views for different sizes.
  • systemExtraLargePortrait first appeared in visionOS 26 and is now also supported on iOS 27, macOS 27, and iPadOS 27. This extra-large portrait size is well suited to feeds or schedules.

(14:03)

By default, tapping a widget opens the app’s home screen. If the widget displays specific content, provide a Deep Link directly to that content.

struct DailyReadingGoalWidget: Widget {
    let kind = "DailyReadingGoalWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: kind,
            provider: DailyReadingGoalProvider()
        ) { entry in
            DailyReadingGoalView(book: entry.book,
                                 message: entry.message,
                                 timeOfDay: entry.timeOfDay)
            .environment(\.colorScheme, .dark)
            .containerBackground(for: .widget) {
                Background()
            }
            .widgetURL(URL(string: "bookclub://reading/\(book.bookID)"))
        }
        .supportedFamilies([.systemMedium])
    }
}

Key points:

  • .widgetURL attaches a URL to the widget view. When the user taps the widget, the system passes this URL to the app.
  • The URL encodes the book ID. After launch, the app parses the URL and jumps directly to that book’s detail page.
  • A Deep Link preserves context between the widget and the app, so users do not get lost when moving from the widget into the app.

Controlling image rendering in Tinted mode

(18:17)

In Tinted mode, the system tries to convert widget content into a monochrome tint to keep the Home Screen visually consistent. This can break content that needs its original colors, such as photos, covers, and logos.

struct BookCoverImage: View {
    let imageName: String

    var body: some View {
        Image(imageName: bundle: .main)
            .widgetAccentedRenderingMode(.fullColor)
    }
}

Key points:

  • .widgetAccentedRenderingMode(.fullColor) tells the system to render this image in full color and exclude it from tint processing.
  • Another option is .accented, which lets the image participate in the system tint treatment. Icon-like content works well with that option.
  • Overusing .fullColor breaks the visual consistency of Tinted mode. Use it only for core visual elements that truly need original color, such as user avatars, book covers, and brand logos.
  • Backgrounds and other supporting elements should follow the system tint. Do not add .fullColor globally.

Key Takeaways

  • Build a reading tracker widget: Use StaticConfiguration plus the afterDate reload policy to recompute the reading plan every midnight. Support systemMedium for today’s goal and systemExtraLargePortrait for the next three days. Deep Link directly to the current chapter. Entry APIs: StaticConfiguration, TimelineProvider, widgetURL.

  • Build a configurable multi-city weather widget: Use AppIntentConfiguration so users can choose the city to follow, and support multiple widget instances with different configurations. One Home Screen can place three widgets tracking the weather at home, the office, and a travel destination. Entry APIs: AppIntentConfiguration, AppIntent.

  • Build an interactive habit check-in widget: Put a button on the widget and let users complete today’s habit directly. Bind the button to an App Intent that updates the data, then refresh the widget state through WidgetCenter.reloadTimelines. Entry APIs: Button, AppIntent, WidgetCenter.

  • Adapt existing widgets to Tinted and Clear modes: Inspect every widget image. Add .widgetAccentedRenderingMode(.fullColor) to content that must preserve original color, and add .containerBackground(for: .widget) to every widget root view. Use Xcode SwiftUI Preview to switch rendering modes and verify quickly. Entry APIs: widgetAccentedRenderingMode, containerBackground.

  • Reuse widgets across platforms: iOS widgets are automatically available on CarPlay and macOS as remote widgets. During testing, make sure Deep Links and interactions also work on macOS, and keep interaction feel consistent between mouse clicks and touch. Entry APIs: supportedFamilies, widgetURL.

Comments

GitHub Issues · utterances