Logo for tanaschita.com

Building configurable widgets with WidgetKit and SwiftUI

Learn how to let users customize a WidgetKit widget with App Intents.

updated on 20 Jul 2026 · 5 min read

In the previous article on building widgets with WidgetKit and SwiftUI, we looked at the basic structure of a WidgetKit widget.

In this article, we'll look at configurable widgets. A configurable widget lets users choose values that affect what the widget displays. For example, a finance app could let users choose an account, a task app could let users choose a project, and a quote app could let users choose a category.

We'll use a simple quote widget as an example. The user will be able to choose a quote category when adding or editing the widget, and the widget will show a quote for that selected category.

The flow is: the user chooses a value, WidgetKit stores it in the intent, the provider receives the intent, and the widget renders an entry built from that value.

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

Defining the configurable value

The first step is to define the value the user can choose. In our example, this value is the quote category.

For a fixed list of options, we can use an enum that conforms to AppEnum. Dynamic options are also possible when the available values come from app data, and we'll look at that later in the article.

import AppIntents
enum QuoteCategory: String, AppEnum {
case motivation
case focus
case mindfulness
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Category")
static var caseDisplayRepresentations: [QuoteCategory: DisplayRepresentation] = [
.motivation: "Motivation",
.focus: "Focus",
.mindfulness: "Mindfulness"
]
}

The display representations define how the enum cases appear in the widget configuration UI.

Exposing the value to WidgetKit

Next, we define a type that conforms to WidgetConfigurationIntent. The intent describes the values WidgetKit should show when the user configures the widget.

We can use the QuoteCategory enum as a parameter:

struct QuoteWidgetIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Quote Widget"
static var description = IntentDescription("Shows a quote from the selected category.")
@Parameter(title: "Category")
var category: QuoteCategory = .motivation
}

The @Parameter property is the value users can change. Here, the user can choose a quote category, and the widget can later use that selected category to decide which quote to display.

Connecting the intent to the widget

For a configurable widget, we use AppIntentConfiguration:

import AppIntents
import SwiftUI
import WidgetKit
struct QuoteWidget: Widget {
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.")
.supportedFamilies([.systemSmall, .systemMedium])
}
}

WidgetKit uses the intent type to show the widget configuration UI and to pass the selected values to the timeline provider.

Using the selected value

A timeline entry contains the data that the widget view needs to render a specific state:

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

Because the widget is configurable, the provider conforms to AppIntentTimelineProvider. Its snapshot and timeline methods receive the selected configuration, which is where the user choice becomes useful:

struct QuoteTimelineProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> QuoteEntry {
QuoteEntry(
date: Date(),
category: .motivation,
quote: "Small steps count."
)
}
func snapshot(for configuration: QuoteWidgetIntent, in context: Context) async -> QuoteEntry {
entry(for: configuration.category)
}
func timeline(for configuration: QuoteWidgetIntent, in context: Context) async -> Timeline<QuoteEntry> {
let entry = entry(for: configuration.category)
let nextUpdate = Date().addingTimeInterval(60 * 60)
return Timeline(entries: [entry], policy: .after(nextUpdate))
}
private func entry(for category: QuoteCategory) -> QuoteEntry {
QuoteEntry(
date: Date(),
category: category,
quote: quote(for: category)
)
}
private func quote(for category: QuoteCategory) -> String {
switch category {
case .motivation:
return "Small steps count."
case .focus:
return "One thing at a time."
case .mindfulness:
return "Take a breath before moving on."
}
}
}

The important part is configuration.category. It contains the category selected by the user, so the provider can create a timeline entry for that category.

Building the widget view

The widget view receives the timeline entry and renders the selected content:

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

In a real app, the provider could load the quote from shared app data, a local database, or another persistence layer that the app and widget extension can both access.

When options come from app data

An enum works well when the available choices are known ahead of time. If the options come from user data, such as projects, accounts, or lists, the configuration can use App Intents entities and queries instead.

For example, a task app might let users choose one of their own projects. In that case, the widget configuration needs to load the available projects dynamically instead of defining them as enum cases.

Summary

To build a configurable widget, we define a WidgetConfigurationIntent, use it with AppIntentConfiguration, and read the selected values in an AppIntentTimelineProvider.

For simple fixed choices, an AppEnum is often enough. For user-generated choices, App Intents entities are a better fit because the available options can be loaded dynamically.

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