Logo for tanaschita.com

Building widgets with WidgetKit and SwiftUI

Learn how to create widgets for an iOS application with WidgetKit and SwiftUI.

20 Jul 2026 · 8 min read

Widgets give users a lightweight way to see useful information from an app without opening it first. A good widget is usually focused on one small job: showing today's progress, the next event, the current status, or a shortcut into a specific part of the app.

WidgetKit handles the system integration, scheduling, rendering, and configuration. We provide the data, decide when it should refresh, and build the view with SwiftUI.

Before building a widget, it helps to decide what information is useful at a glance. Widgets work best when they show a small amount of timely content or provide a shortcut to a focused action. If the user needs to make several decisions, the app itself is usually the better place for that flow.

In this article, we'll look at how to add a widget extension, configure a widget, provide timeline entries, and build the widget's SwiftUI view.

We'll use a simple quote widget as an example. The same structure can be applied to other types of widgets, such as a task summary, a weather overview, or a project status widget.

Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE

Adding a widget extension

A widget lives in its own app extension. To add one to an existing iOS app, we can use Xcode's Widget Extension template from File > New > Target and enter a name for the extension.

Xcode creates a new target with a widget entry point. The main widget type conforms to the Widget protocol and is marked with @main:

import SwiftUI
import WidgetKit
@main
struct ExampleWidget: Widget {
var body: some WidgetConfiguration {
// Return the widget configuration.
}
}

The configuration describes what kind of widget we are building, which timeline provider supplies its data, and which SwiftUI view renders the widget.

Choosing a widget configuration

Static widgets

For many widgets, StaticConfiguration is enough. It works well when the widget does not need user-configurable options.

var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.example.QuoteWidget",
provider: ExampleTimelineProvider()
) { entry in
QuoteWidgetView(entry: entry)
}
.configurationDisplayName("Quote Widget")
.description("Shows the latest quote of the day.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}

Here, kind identifies the widget, the provider supplies its timeline entries, and the closure builds the SwiftUI view. The remaining modifiers define how the widget appears when users add or edit it.

Configurable widgets

If the widget should be configurable by the user, we can use AppIntentConfiguration instead. This is useful when the widget content depends on a selected project, category, account, or any other user-provided option.

var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: "com.example.QuoteWidget",
intent: QuoteWidgetIntent.self,
provider: QuoteTimelineProvider()
) { entry in
QuoteWidgetView(entry: entry)
}
.configurationDisplayName("Quote")
.description("Shows a quote for the selected category.")
}

The configuration looks similar to the static one, but it also receives an intent type. The intent describes the values users can configure:

import AppIntents
import WidgetKit
enum QuoteCategory: String, AppEnum {
case motivation
case focus
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Category")
static var caseDisplayRepresentations: [QuoteCategory: DisplayRepresentation] = [
.motivation: "Motivation",
.focus: "Focus"
]
}
struct QuoteWidgetIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Quote Widget"
@Parameter(title: "Category")
var category: QuoteCategory = .motivation
}

In this example, the user can choose between predefined quote categories when adding or editing the widget. The selected category is passed to the provider as part of the QuoteWidgetIntent configuration, so the provider can create entries for the chosen category.

For a more detailed walkthrough, check out the article on how to build a configurable widget with WidgetKit and SwiftUI.

Providing timeline entries

Widgets don't run continuously in the background. Instead, WidgetKit asks the timeline provider for entries and decides when to render them.

A timeline entry is a small model object that conforms to TimelineEntry. It must contain a date and can include any other values the widget view needs:

struct QuoteEntry: TimelineEntry {
let date: Date
let quote: String
}

For a static widget, the provider conforms to TimelineProvider:

struct ExampleTimelineProvider: TimelineProvider {
func placeholder(in context: Context) -> QuoteEntry {
QuoteEntry(date: Date(), quote: "Quote of the day")
}
func getSnapshot(in context: Context, completion: @escaping (QuoteEntry) -> Void) {
let entry = QuoteEntry(date: Date(), quote: "Quote of the day")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<QuoteEntry>) -> Void) {
let entry = QuoteEntry(date: Date(), quote: "Small steps count.")
let nextUpdate = Date().addingTimeInterval(60 * 60)
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}

Let's look at the methods more closely:

In practice, placeholder should be generic, while snapshot can use realistic sample or current data.

The reload policy is only a request. WidgetKit decides the exact refresh timing based on system conditions, so a widget should not depend on second-by-second updates.

For a configurable widget, the idea is the same, but the provider conforms to AppIntentTimelineProvider. Its snapshot and timeline methods receive the selected configuration, which lets us use values like configuration.category when creating the entries.

Building the widget view with SwiftUI

The widget view is a regular SwiftUI view, but it should stay focused and lightweight. It receives a timeline entry and renders the current state:

struct QuoteWidgetView: View {
let entry: QuoteEntry
var body: some View {
Text(entry.quote)
.font(.headline)
.containerBackground(.background, for: .widget)
}
}

Widgets can appear in different sizes and contexts. We can use the widgetFamily environment value to adapt the layout:

struct QuoteWidgetView: View {
@Environment(\.widgetFamily) private var family
let entry: QuoteEntry
var body: some View {
switch family {
case .systemSmall:
Text(entry.quote)
.font(.headline)
case .systemMedium:
VStack(alignment: .leading) {
Text("Quote")
.font(.caption)
Text(entry.quote)
.font(.headline)
}
default:
Text(entry.quote)
}
}
}

When possible, keep the view resilient. The widget might render with placeholder data, stale data, or in a smaller family than expected.

Because the widget view is built with SwiftUI, we can preview different entries and widget families in Xcode while adjusting the layout.

Opening the app from a widget

The simplest interaction is opening the app when the user taps the widget. To open a specific screen, the widget needs a URL that the app can handle. This can be a custom URL scheme like myapp://quote/today, or a universal link if the same destination should also open from outside the app.

For a single destination, we can attach a URL to a view in the widget hierarchy:

Text(entry.quote)
.widgetURL(URL(string: "myapp://quote/today"))

In SwiftUI, the app can handle the URL with onOpenURL(perform:) and route the user to the matching screen:

@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// Route to the matching screen.
}
}
}
}

For widgets that show multiple tappable areas, we can use Link:

Link(destination: URL(string: "myapp://quote/today")!) {
Text(entry.quote)
}

Adding interactive controls

On newer OS versions, widgets can also support simple interactions without launching the app. For example, a button can run an AppIntent to update shared app data.

Interactive widgets are best suited for small, predictable actions. If an action requires a longer flow, authentication, or more context, opening the app is usually the better experience.

Sharing data with the widget extension

Because the widget runs in a separate extension, it needs access to the data it displays. For simple values, this can be done with shared UserDefaults in an App Group. For larger or structured data, we can use a shared container or another persistence setup that both the app and widget extension can access.

Reloading widget timelines

When app data changes, we can ask WidgetKit to reload the widget's timeline:

WidgetCenter.shared.reloadTimelines(ofKind: "com.example.QuoteWidget")

To reload all timelines for the app's widgets, we can call:

WidgetCenter.shared.reloadAllTimelines()

This is useful after saving data that is displayed in the widget. Still, the system remains in control of the final refresh timing, so the widget should always display a reasonable state until the next update happens.

Summary

To build a WidgetKit widget, we create a widget extension, choose a configuration, provide timeline entries, and render the current entry with SwiftUI.

For simple widgets, StaticConfiguration and TimelineProvider are enough. For user-configurable widgets, AppIntentConfiguration and AppIntentTimelineProvider provide a modern way to model the configuration. From there, we can decide whether tapping the widget should open the app, or whether a small action can be handled directly with an AppIntent.

Sponsorship logo
Preparing for a technical iOS job interview
Preparing for a technical iOS Job Interview with over 300 questions & answers. Covering Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, iOS File System, Core Data, Concurrency with async/await, Security, Automated Testing, Dependency Management, AI & Machine Learning and more.
LEARN MORE
Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE
Sponsorship logo
Become a sponsor of tanaschita.com
By publishing an article on different iOS topics every week, tanaschita.com is constantly growing in the developer community and may provide a great audience for you as a sponsor.
CLICK TO LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

activitykit

widgetkit

swiftui

swift

ios

Getting started with Live Activities in SwiftUI

Build a public transport journey tracker for the Lock Screen and Dynamic Island with ActivityKit.

07 Sep 2026 · 7 min read

Latest articles and tips

© 2026 tanaschita.com

Privacy policy

Impressum